From eb04369034c4648f0d9f617f845fa93670c02222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 26 Apr 2021 19:26:43 +0200 Subject: [PATCH 001/211] Upgrading packages --- .../KGySoft.Drawing.DebuggerVisualizers.Package.csproj | 8 ++++---- .../packages.config | 4 ++-- .../KGySoft.Drawing.DebuggerVisualizers.Test.csproj | 6 +++--- .../KGySoft.Drawing.DebuggerVisualizers.csproj | 6 +++--- .../KGySoft.Drawing.ImagingTools.csproj | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj b/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj index d52de91..94ea8d8 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj @@ -101,11 +101,11 @@ False False - - ..\..\packages\KGySoft.CoreLibraries.5.4.0\lib\net45\KGySoft.CoreLibraries.dll + + ..\..\packages\KGySoft.CoreLibraries.5.6.1\lib\net45\KGySoft.CoreLibraries.dll - - ..\..\packages\KGySoft.Drawing.5.3.0\lib\net45\KGySoft.Drawing.dll + + ..\..\packages\KGySoft.Drawing.5.3.1\lib\net45\KGySoft.Drawing.dll ..\..\packages\VSSDK.GraphModel.11.0.4\lib\net45\Microsoft.VisualStudio.GraphModel.dll diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/packages.config b/KGySoft.Drawing.DebuggerVisualizers.Package/packages.config index 63f9ab4..9a46723 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/packages.config +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/packages.config @@ -1,7 +1,7 @@  - - + + diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj b/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj index f57228d..f5d8660 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj @@ -19,9 +19,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj b/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj index dea5e4e..a939ad7 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj @@ -17,9 +17,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj index 19d95ba..f0ab754 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj @@ -21,9 +21,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 98577077762419b545851e1e06724e2256daa9c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 28 Apr 2021 22:26:14 +0200 Subject: [PATCH 002/211] KGySoft.Drawing.Tools: Enabling nullable references --- ...ft.Drawing.DebuggerVisualizers.Test.csproj | 4 +- ...KGySoft.Drawing.DebuggerVisualizers.csproj | 4 +- .../GlobalSuppressions.cs | 2 + .../KGySoft.Drawing.ImagingTools.csproj | 5 +- .../Model/BitmapDataInfo.cs | 4 +- .../Model/CustomPropertiesObject.cs | 16 +- .../Model/CustomPropertyDescriptor.cs | 43 +++-- .../Model/DesignDependencies.cs | 4 +- .../Model/DithererDescriptor.cs | 28 ++- .../Model/GraphicsInfo.cs | 5 +- .../Model/ImageFrameInfo.cs | 2 +- .../Model/ImageInfo.cs | 49 +++-- .../Model/ImageInfoBase.cs | 6 +- .../Model/InstallationInfo.cs | 35 ++-- .../Model/QuantizerDescriptor.cs | 21 ++- .../_Interfaces/ICustomPropertiesProvider.cs | 4 +- KGySoft.Drawing.ImagingTools/Res.cs | 21 +-- .../Diagnostics.CodeAnalysis/Attributes.cs | 29 +++ .../IsExternalInit.cs | 8 + .../View/Controls/CheckGroupBox.cs | 6 +- .../Controls/DrawingProgressStatusStrip.cs | 10 +- .../View/Controls/ImageViewer.cs | 76 ++++---- .../View/Controls/NotificationLabel.cs | 5 +- .../View/Controls/PalettePanel.cs | 14 +- .../View/Controls/ScalingCheckBox.cs | 31 ++- .../View/Controls/ScalingToolStrip.cs | 2 - .../View/Design/DithererStrengthEditor.cs | 4 +- .../View/Design/QuantizerThresholdEditor.cs | 4 +- KGySoft.Drawing.ImagingTools/View/Dialogs.cs | 1 - .../View/Forms/AdjustBrightnessForm.cs | 2 +- .../View/Forms/AdjustColorsFormBase.cs | 14 +- .../View/Forms/AdjustContrastForm.cs | 2 +- .../View/Forms/AdjustGammaForm.cs | 2 +- .../View/Forms/AppMainForm.cs | 13 +- .../View/Forms/BaseForm.cs | 28 ++- .../View/Forms/ColorSpaceForm.cs | 2 +- .../View/Forms/ColorVisualizerForm.cs | 4 +- .../View/Forms/CountColorsForm.cs | 14 +- .../View/Forms/GraphicsVisualizerForm.cs | 8 +- .../View/Forms/ImageVisualizerForm.cs | 6 +- .../View/Forms/ManageInstallationsForm.cs | 4 +- .../View/Forms/MvvmBaseForm.cs | 16 +- .../View/Forms/PaletteVisualizerForm.cs | 4 +- .../View/Forms/ResizeBitmapForm.cs | 10 +- .../View/Forms/TransformBitmapFormBase.cs | 8 +- KGySoft.Drawing.ImagingTools/View/Images.cs | 48 ++--- .../UserControls/ColorVisualizerControl.cs | 47 ++--- .../UserControls/DithererSelectorControl.cs | 4 +- .../DithererStrengthEditorControl.cs | 12 +- .../View/UserControls/MvvmBaseUserControl.cs | 31 +-- .../View/UserControls/PreviewImageControl.cs | 39 ++-- .../UserControls/QuantizerSelectorControl.cs | 4 +- .../QuantizerThresholdEditorControl.cs | 12 +- .../View/WinformsCommandBindingsCollection.cs | 6 +- .../_Extensions/EventHandlerExtensions.cs | 2 +- .../ViewModel/AdjustBrightnessViewModel.cs | 4 +- .../ViewModel/AdjustColorsViewModelBase.cs | 20 +- .../ViewModel/AdjustContrastViewModel.cs | 4 +- .../ViewModel/AdjustGammaViewModel.cs | 4 +- .../BitmapDataVisualizerViewModel.cs | 8 +- .../ViewModel/ColorSpaceViewModel.cs | 41 ++-- .../ViewModel/CountColorsViewModel.cs | 24 +-- .../ViewModel/DefaultViewModel.cs | 16 +- .../ViewModel/DithererSelectorViewModel.cs | 56 +++--- .../ViewModel/GraphicsVisualizerViewModel.cs | 26 +-- .../ViewModel/ImageVisualizerViewModel.cs | 178 +++++++++--------- .../ViewModel/ManageInstallationsViewModel.cs | 26 +-- .../ViewModel/PaletteVisualizerViewModel.cs | 11 +- .../ViewModel/PreviewImageViewModel.cs | 6 +- .../ViewModel/QuantizerSelectorViewModel.cs | 42 ++--- .../ViewModel/ResizeBitmapViewModel.cs | 27 ++- .../ViewModel/TransformBitmapViewModelBase.cs | 48 +++-- .../ViewModel/ViewModelBase.cs | 14 +- .../ViewModel/ViewModelFactory.cs | 48 +++-- .../WinApi/Constants.cs | 2 + .../WinApi/MEMORYSTATUSEX.cs | 1 + .../_Classes/DrawingProgressManager.cs | 5 +- .../_Classes/InstallationManager.cs | 8 +- .../CommandBindingsCollectionExtensions.cs | 8 +- .../_Extensions/PixelFormatExtensions.cs | 3 + KGySoft.Drawing.Tools.sln.DotSettings | 13 +- 81 files changed, 756 insertions(+), 672 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/System/Diagnostics.CodeAnalysis/Attributes.cs create mode 100644 KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/IsExternalInit.cs diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj b/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj index f5d8660..ea1500c 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj @@ -1,8 +1,8 @@  - net35;net40;net45;netcoreapp3.0 - + + net45 false KGySoft.Drawing.DebuggerVisualizers.Test true diff --git a/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj b/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj index a939ad7..5ed22bc 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj @@ -1,8 +1,8 @@  - net35;net40;net45;netcoreapp3.0 - + + net45 false KGySoft.Drawing.DebuggerVisualizers bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml diff --git a/KGySoft.Drawing.ImagingTools/GlobalSuppressions.cs b/KGySoft.Drawing.ImagingTools/GlobalSuppressions.cs index 4c4658c..dec7e59 100644 --- a/KGySoft.Drawing.ImagingTools/GlobalSuppressions.cs +++ b/KGySoft.Drawing.ImagingTools/GlobalSuppressions.cs @@ -7,3 +7,5 @@ [assembly: SuppressMessage("Style", "IDE0063:Use simple 'using' statement", Justification = "Decided individually")] [assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "Decided individually")] +[assembly: SuppressMessage("Style", "IDE0057:Use range operator", Justification = "Cannot be used because it is not supported in every targeted platform")] +[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "Decided individually")] diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj index f0ab754..5d226ed 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj @@ -1,8 +1,8 @@  - net35;net40;net45;netcoreapp3.0 - + + net35;net45;net5.0-windows false KGySoft.Drawing.ImagingTools bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml @@ -18,6 +18,7 @@ true WinExe app.manifest + enable diff --git a/KGySoft.Drawing.ImagingTools/Model/BitmapDataInfo.cs b/KGySoft.Drawing.ImagingTools/Model/BitmapDataInfo.cs index 19ef731..d3049d3 100644 --- a/KGySoft.Drawing.ImagingTools/Model/BitmapDataInfo.cs +++ b/KGySoft.Drawing.ImagingTools/Model/BitmapDataInfo.cs @@ -35,12 +35,12 @@ public sealed class BitmapDataInfo : IDisposable /// /// Gets or sets a that represents the content of the . /// - public Bitmap BackingImage { get; set; } + public Bitmap? BackingImage { get; set; } /// /// Gets or sets the bitmap data. /// - public BitmapData BitmapData { get; set; } + public BitmapData? BitmapData { get; set; } #endregion diff --git a/KGySoft.Drawing.ImagingTools/Model/CustomPropertiesObject.cs b/KGySoft.Drawing.ImagingTools/Model/CustomPropertiesObject.cs index 2b42017..374fc4a 100644 --- a/KGySoft.Drawing.ImagingTools/Model/CustomPropertiesObject.cs +++ b/KGySoft.Drawing.ImagingTools/Model/CustomPropertiesObject.cs @@ -58,7 +58,7 @@ internal CustomPropertiesObject(CustomPropertiesObject other, IEnumerable true; - protected override bool CanSetProperty(string propertyName, object value) => true; + protected override bool CanSetProperty(string propertyName, object? value) => true; #endregion @@ -66,21 +66,21 @@ internal CustomPropertiesObject(CustomPropertiesObject other, IEnumerable TypeDescriptor.GetAttributes(this, true); string ICustomTypeDescriptor.GetClassName() => TypeDescriptor.GetClassName(this, true); - string ICustomTypeDescriptor.GetComponentName() => TypeDescriptor.GetComponentName(this, true); + string? ICustomTypeDescriptor.GetComponentName() => TypeDescriptor.GetComponentName(this, true); TypeConverter ICustomTypeDescriptor.GetConverter() => TypeDescriptor.GetConverter(this, true); - EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(this, true); - PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(this, true); - object ICustomTypeDescriptor.GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(this, editorBaseType, true); + EventDescriptor? ICustomTypeDescriptor.GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(this, true); + PropertyDescriptor? ICustomTypeDescriptor.GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(this, true); + object? ICustomTypeDescriptor.GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(this, editorBaseType, true); EventDescriptorCollection ICustomTypeDescriptor.GetEvents() => TypeDescriptor.GetEvents(this, true); EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) => TypeDescriptor.GetEvents(this, attributes, true); object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) => this; PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() => propertyDescriptors; - PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) + PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[]? attributes) => new PropertyDescriptorCollection(propertyDescriptors.Cast().Where(p => attributes == null || attributes.Any(a => p.Attributes.Contains(a))).ToArray()); void ICustomPropertiesProvider.ResetValue(string propertyName) => ResetProperty(propertyName); - void ICustomPropertiesProvider.SetValue(string propertyName, object value) => Set(value, true, propertyName); - object ICustomPropertiesProvider.GetValue(string propertyName, object defaultValue) => Get(defaultValue, propertyName); + void ICustomPropertiesProvider.SetValue(string propertyName, object? value) => Set(value, true, propertyName); + object? ICustomPropertiesProvider.GetValue(string propertyName, object? defaultValue) => Get(defaultValue, propertyName); #endregion diff --git a/KGySoft.Drawing.ImagingTools/Model/CustomPropertyDescriptor.cs b/KGySoft.Drawing.ImagingTools/Model/CustomPropertyDescriptor.cs index 6f05b57..4ceb647 100644 --- a/KGySoft.Drawing.ImagingTools/Model/CustomPropertyDescriptor.cs +++ b/KGySoft.Drawing.ImagingTools/Model/CustomPropertyDescriptor.cs @@ -43,13 +43,13 @@ private class PickValueConverter : TypeConverter #region Fields private readonly TypeConverter wrappedConverter; - private readonly object[] allowedValues; + private readonly object?[] allowedValues; #endregion #region Constructors - internal PickValueConverter(TypeConverter converter, object[] allowedValues) + internal PickValueConverter(TypeConverter converter, object?[] allowedValues) { wrappedConverter = converter; this.allowedValues = allowedValues; @@ -61,8 +61,8 @@ internal PickValueConverter(TypeConverter converter, object[] allowedValues) public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => wrappedConverter.CanConvertFrom(context, sourceType); public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) => wrappedConverter.CanConvertTo(context, destinationType); - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) => wrappedConverter.ConvertFrom(context, culture, value); - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) => wrappedConverter.ConvertTo(context, culture, value, destinationType); + public override object? ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) => wrappedConverter.ConvertFrom(context, culture, value); + public override object? ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) => wrappedConverter.ConvertTo(context, culture, value, destinationType); public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => true; public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) => true; public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) => new StandardValuesCollection(allowedValues); @@ -78,9 +78,9 @@ internal PickValueConverter(TypeConverter converter, object[] allowedValues) private readonly HashSet attributes; - private AttributeCollection cachedAttributes; - private PickValueConverter converter; - private Type editor; + private AttributeCollection? cachedAttributes; + private PickValueConverter? converter; + private Type? editor; #endregion @@ -99,7 +99,7 @@ public override AttributeCollection Attributes } } - public override TypeConverter Converter => AllowedValues == null ? base.Converter : (converter ??= new PickValueConverter(base.Converter, AllowedValues)); + public override TypeConverter? Converter => AllowedValues == null || base.Converter == null ? base.Converter : converter ??= new PickValueConverter(base.Converter, AllowedValues); public override Type ComponentType => typeof(ICustomPropertiesProvider); public override bool IsReadOnly => false; public override Type PropertyType { get; } @@ -108,27 +108,29 @@ public override AttributeCollection Attributes #region Internal Properties - internal new string Category + internal new string? Category { get => base.Category; set { cachedAttributes = null; - attributes.Add(new CategoryAttribute(value)); + if (value != null) + attributes.Add(new CategoryAttribute(value)); } } - internal new string Description + internal new string? Description { get => base.Description; set { cachedAttributes = null; - attributes.Add(new DescriptionAttribute(value)); + if (value != null) + attributes.Add(new DescriptionAttribute(value)); } } - internal Type UITypeEditor + internal Type? UITypeEditor { get => editor ??= GetEditor(typeof(UITypeEditor))?.GetType(); set @@ -137,13 +139,14 @@ internal Type UITypeEditor return; cachedAttributes = null; editor = value; - attributes.Add(new EditorAttribute(value, typeof(UITypeEditor))); + if (value != null) + attributes.Add(new EditorAttribute(value, typeof(UITypeEditor))); } } - internal object DefaultValue { get; set; } - internal object[] AllowedValues { get; set; } - internal Func AdjustValue { get; set; } + internal object? DefaultValue { get; set; } + internal object?[]? AllowedValues { get; set; } + internal Func? AdjustValue { get; set; } #endregion @@ -174,16 +177,16 @@ public CustomPropertyDescriptor(CustomPropertyDescriptor other) : base(other) public override bool CanResetValue(object component) => ShouldSerializeValue(component); public override void ResetValue(object component) => ((ICustomPropertiesProvider)component).ResetValue(Name); public override void SetValue(object component, object value) => ((ICustomPropertiesProvider)component).SetValue(Name, DoAdjustValue(value)); - public override object GetValue(object component) => DoAdjustValue(((ICustomPropertiesProvider)component).GetValue(Name, DefaultValue)); + public override object? GetValue(object component) => DoAdjustValue(((ICustomPropertiesProvider)component).GetValue(Name, DefaultValue)); public override string ToString() => $"{Name}: {PropertyType}"; #endregion #region Private Methods - private object DoAdjustValue(object value) + private object? DoAdjustValue(object? value) => AdjustValue != null ? AdjustValue.Invoke(value) - : !AllowedValues.IsNullOrEmpty() && !value.In(AllowedValues) ? AllowedValues[0] + : !AllowedValues.IsNullOrEmpty() && !value.In(AllowedValues) ? AllowedValues![0] : value == null && PropertyType.IsValueType ? DefaultValue ?? Activator.CreateInstance(PropertyType) : value; diff --git a/KGySoft.Drawing.ImagingTools/Model/DesignDependencies.cs b/KGySoft.Drawing.ImagingTools/Model/DesignDependencies.cs index c35c00f..15736e6 100644 --- a/KGySoft.Drawing.ImagingTools/Model/DesignDependencies.cs +++ b/KGySoft.Drawing.ImagingTools/Model/DesignDependencies.cs @@ -26,8 +26,8 @@ internal static class DesignDependencies { #region Properties - internal static Type QuantizerThresholdEditor { get; set; } - internal static Type DithererStrengthEditor { get; set; } + internal static Type? QuantizerThresholdEditor { get; set; } + internal static Type? DithererStrengthEditor { get; set; } #endregion } diff --git a/KGySoft.Drawing.ImagingTools/Model/DithererDescriptor.cs b/KGySoft.Drawing.ImagingTools/Model/DithererDescriptor.cs index 5aedea3..ba0795d 100644 --- a/KGySoft.Drawing.ImagingTools/Model/DithererDescriptor.cs +++ b/KGySoft.Drawing.ImagingTools/Model/DithererDescriptor.cs @@ -18,14 +18,12 @@ using System; using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.Linq; using System.Reflection; +#if NETFRAMEWORK using KGySoft.CoreLibraries; +#endif using KGySoft.Drawing.Imaging; -using KGySoft.Reflection; #endregion @@ -67,7 +65,7 @@ internal sealed class DithererDescriptor #region Constructors - internal DithererDescriptor(Type type, string propertyName) : this(type.GetProperty(propertyName)) + internal DithererDescriptor(Type type, string propertyName) : this(type.GetProperty(propertyName)!) { } @@ -83,11 +81,11 @@ internal DithererDescriptor(MemberInfo member) break; case PropertyInfo property: if (property.DeclaringType == typeof(OrderedDitherer)) - AddMethodChain(chain, parameters, typeof(OrderedDitherer).GetMethod(nameof(OrderedDitherer.ConfigureStrength))); + AddMethodChain(chain, parameters, typeof(OrderedDitherer).GetMethod(nameof(OrderedDitherer.ConfigureStrength))!); else if (property.DeclaringType == typeof(ErrorDiffusionDitherer)) { - AddMethodChain(chain, parameters, typeof(ErrorDiffusionDitherer).GetMethod(nameof(ErrorDiffusionDitherer.ConfigureProcessingDirection))); - AddMethodChain(chain, parameters, typeof(ErrorDiffusionDitherer).GetMethod(nameof(ErrorDiffusionDitherer.ConfigureErrorDiffusionMode))); + AddMethodChain(chain, parameters, typeof(ErrorDiffusionDitherer).GetMethod(nameof(ErrorDiffusionDitherer.ConfigureProcessingDirection))!); + AddMethodChain(chain, parameters, typeof(ErrorDiffusionDitherer).GetMethod(nameof(ErrorDiffusionDitherer.ConfigureErrorDiffusionMode))!); } break; @@ -108,7 +106,7 @@ internal DithererDescriptor(MemberInfo member) private static void AddParameters(List descriptors, ParameterInfo[] reflectedParameters) { foreach (ParameterInfo pi in reflectedParameters) - descriptors.Add(parametersMapping.GetValueOrDefault(pi.Name) ?? throw new InvalidOperationException(Res.InternalError($"Unexpected parameter: {pi.Name}"))); + descriptors.Add(parametersMapping.GetValueOrDefault(pi.Name!) ?? throw new InvalidOperationException(Res.InternalError($"Unexpected parameter: {pi.Name}"))); } private static void AddMethodChain(List chain, List parameters, MethodInfo method) @@ -124,20 +122,20 @@ private static void AddMethodChain(List chain, List InvokeChain[0] is ConstructorInfo ctor - ? ctor.DeclaringType.Name - : $"{InvokeChain[0].DeclaringType.Name}.{InvokeChain[0].Name}"; + ? ctor.DeclaringType!.Name + : $"{InvokeChain[0].DeclaringType!.Name}.{InvokeChain[0].Name}"; #endregion #region Internal Methods - internal object[] EvaluateParameters(ParameterInfo[] parameters, CustomPropertiesObject values) + internal object?[] EvaluateParameters(ParameterInfo[] parameters, CustomPropertiesObject values) { - var result = new object[parameters.Length]; + var result = new object?[parameters.Length]; for (int i = 0; i < result.Length; i++) { - string paramName = parameters[i].Name; - result[i] = Parameters.Find(d => d.Name == paramName).GetValue(values); + string paramName = parameters[i].Name!; + result[i] = Parameters.Find(d => d.Name == paramName)!.GetValue(values); } return result; diff --git a/KGySoft.Drawing.ImagingTools/Model/GraphicsInfo.cs b/KGySoft.Drawing.ImagingTools/Model/GraphicsInfo.cs index 1577a96..730d815 100644 --- a/KGySoft.Drawing.ImagingTools/Model/GraphicsInfo.cs +++ b/KGySoft.Drawing.ImagingTools/Model/GraphicsInfo.cs @@ -19,7 +19,6 @@ using System; using System.Drawing; using System.Drawing.Drawing2D; -using System.Text; #endregion @@ -36,12 +35,12 @@ public sealed class GraphicsInfo : IDisposable /// /// Gets or sets a that represents the content of the corresponding . /// - public Bitmap GraphicsImage { get; set; } + public Bitmap? GraphicsImage { get; set; } /// /// Gets or sets the transformation of the corresponding . /// - public Matrix Transform { get; set; } + public Matrix? Transform { get; set; } /// /// Gets or sets the original visible clip bounds in pixels, without applying any transformation. diff --git a/KGySoft.Drawing.ImagingTools/Model/ImageFrameInfo.cs b/KGySoft.Drawing.ImagingTools/Model/ImageFrameInfo.cs index d818bc6..5f03775 100644 --- a/KGySoft.Drawing.ImagingTools/Model/ImageFrameInfo.cs +++ b/KGySoft.Drawing.ImagingTools/Model/ImageFrameInfo.cs @@ -45,7 +45,7 @@ public sealed class ImageFrameInfo : ImageInfoBase /// Initializes a new instance of the class from a . /// /// The bitmap that contains the image of the current frame. - public ImageFrameInfo(Bitmap bitmap) + public ImageFrameInfo(Bitmap? bitmap) { Image = bitmap; InitMeta(bitmap); diff --git a/KGySoft.Drawing.ImagingTools/Model/ImageInfo.cs b/KGySoft.Drawing.ImagingTools/Model/ImageInfo.cs index 7d3321a..1ed512b 100644 --- a/KGySoft.Drawing.ImagingTools/Model/ImageInfo.cs +++ b/KGySoft.Drawing.ImagingTools/Model/ImageInfo.cs @@ -50,19 +50,17 @@ public sealed class ImageInfo : ImageInfoBase /// /// Gets or sets an instance associated with this instance. /// - public Icon Icon { get => Get(); set => Set(value); } + public Icon? Icon { get => Get(); set => Set(value); } /// /// Gets or sets a file name associated with this instance. /// - public string FileName { get => Get(); set => Set(value); } + public string? FileName { get => Get(); set => Set(value); } /// /// If this instance represents a multi-frame image, then gets or sets the frames belong to the image. /// - [SuppressMessage("Performance", "CA1819:Properties should not return arrays", - Justification = "This is a descriptor class. It is expected that this property or the elements are set. The IsValid property gets whether this instance is valid, including frames.")] - public ImageFrameInfo[] Frames { get => Get(); set => Set(value); } + public ImageFrameInfo[]? Frames { get => Get(); set => Set(value); } /// /// Gets whether this instance represents a multi-frame image and has frames. @@ -99,7 +97,7 @@ public ImageInfo(ImageInfoType imageType) /// Initializes a new instance of the class from an . /// /// The image to be used for the initialization. - public ImageInfo(Image image) + public ImageInfo(Image? image) { InitFromImage(image); SetModified(false); @@ -109,7 +107,7 @@ public ImageInfo(Image image) /// Initializes a new instance of the class from an . /// /// The icon to be used for the initialization. - public ImageInfo(Icon icon) + public ImageInfo(Icon? icon) { InitFromIcon(icon); SetModified(false); @@ -128,9 +126,9 @@ public ImageInfo(Icon icon) /// An that represents the possible compound image of this instance. /// When a new image is created, then the return value will be the new value of the property as well. /// The object is in an invalid state (the property returns ). - public Image GetCreateImage() + public Image? GetCreateImage() { - Image image = Image; + Image? image = Image; if (image != null) return image; if (Type == ImageInfoType.None || !(Type == ImageInfoType.Icon || HasFrames)) @@ -145,9 +143,9 @@ public Image GetCreateImage() /// An that represents the possible icon of this instance. /// When a new icon is created, then the return value will be the new value of the property as well. /// The object is in an invalid state (the property returns ). - public Icon GetCreateIcon() + public Icon? GetCreateIcon() { - Icon icon = Icon; + Icon? icon = Icon; if (icon != null) return icon; if (Type == ImageInfoType.None) @@ -185,12 +183,12 @@ protected override ValidationResultsCollection DoValidation() return new ValidationResultsCollection(); ValidationResultsCollection result = base.DoValidation(); - ImageFrameInfo[] frames = Frames; + ImageFrameInfo[]? frames = Frames; if (Type.In(ImageInfoType.Pages, ImageInfoType.Animation, ImageInfoType.MultiRes)) { if (frames.IsNullOrEmpty()) result.AddError(nameof(Frames), PublicResources.CollectionEmpty); - else if (frames.Any(f => f.Image == null)) + else if (frames!.Any(f => f.Image == null)) result.AddError(nameof(Frames), PublicResources.ArgumentContainsNull); } @@ -227,14 +225,15 @@ protected override void Dispose(bool disposing) [SuppressMessage("ReSharper", "PossibleUnintendedReferenceComparison", Justification = "The dimension variable is compared with the references we set earlier")] - private void InitFromImage(Image image) + [SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "ReSharper issue")] + private void InitFromImage(Image? image) { if (image == null) return; InitMeta(image); Image = image; - Bitmap bmp = image as Bitmap; + Bitmap? bmp = image as Bitmap; ImageFrameInfo[] frames; // icon @@ -260,7 +259,7 @@ private void InitFromImage(Image image) } // other image: check if it has multiple frames - FrameDimension dimension = null; + FrameDimension? dimension = null; Guid[] dimensions = image.FrameDimensionsList; if (dimensions.Length > 0) { @@ -281,7 +280,7 @@ private void InitFromImage(Image image) } // multiple frames - byte[] times = null; + byte[]? times = null; Bitmap bitmap = (Bitmap)image; Type = dimension == FrameDimension.Time ? ImageInfoType.Animation : dimension == FrameDimension.Page ? ImageInfoType.Pages @@ -289,7 +288,7 @@ private void InitFromImage(Image image) // in case of animation there is a compound image if (dimension == FrameDimension.Time) - times = image.GetPropertyItem(0x5100).Value; + times = image.GetPropertyItem(0x5100)?.Value; frames = new ImageFrameInfo[frameCount]; Frames = frames; @@ -308,7 +307,7 @@ private void InitFromImage(Image image) image.SelectActiveFrame(dimension, 0); } - private void InitFromIcon(Icon icon) + private void InitFromIcon(Icon? icon) { #region Local Methods @@ -345,7 +344,7 @@ static void InitIconMeta(IconInfo iconInfo, ImageInfoBase imageInfo) return; } - Bitmap[] iconImages = icon.ExtractBitmaps(); + Bitmap?[] iconImages = icon.ExtractBitmaps(); Debug.Assert(iconInfo.Length == iconImages.Length); var frames = new ImageFrameInfo[iconInfo.Length]; for (int i = 0; i < frames.Length; i++) @@ -381,7 +380,7 @@ private Image GenerateImage() { case ImageInfoType.Pages: var ms = new MemoryStream(); - Frames.Select(f => f.Image).SaveAsMultipageTiff(ms); + Frames!.Select(f => f.Image!).SaveAsMultipageTiff(ms); ms.Position = 0; return new Bitmap(ms); @@ -389,12 +388,12 @@ private Image GenerateImage() case ImageInfoType.Icon: try { - return GetCreateIcon().ToMultiResBitmap(); + return GetCreateIcon()!.ToMultiResBitmap(); } catch (ArgumentException) { // In Windows XP it can happen that multi-res bitmap throws an exception even if PNG images are uncompressed - return GetCreateIcon().ExtractNearestBitmap(new Size(UInt16.MaxValue, UInt16.MaxValue), PixelFormat.Format32bppArgb); + return GetCreateIcon()!.ExtractNearestBitmap(new Size(UInt16.MaxValue, UInt16.MaxValue), PixelFormat.Format32bppArgb); } case ImageInfoType.Animation: @@ -415,12 +414,12 @@ private Icon GenerateIcon() throw new InvalidOperationException($"{error.PropertyName}: {error.Message}"); } - return !HasFrames ? Image.ToIcon() : Icons.Combine(Frames.Select(f => (Bitmap)f.Image)); + return !HasFrames ? Image!.ToIcon() : Icons.Combine(Frames!.Select(f => (Bitmap)f.Image!)); } private void FreeFrames() { - ImageFrameInfo[] frames = Frames; + ImageFrameInfo[]? frames = Frames; if (frames == null) return; foreach (ImageFrameInfo frame in frames) diff --git a/KGySoft.Drawing.ImagingTools/Model/ImageInfoBase.cs b/KGySoft.Drawing.ImagingTools/Model/ImageInfoBase.cs index e9dd2fe..e2296cc 100644 --- a/KGySoft.Drawing.ImagingTools/Model/ImageInfoBase.cs +++ b/KGySoft.Drawing.ImagingTools/Model/ImageInfoBase.cs @@ -43,7 +43,7 @@ public abstract class ImageInfoBase : ValidatingObjectBase /// Gets or sets the image to be displayed or saved /// when debugging the corresponding or instance. /// - public Image Image { get => Get(); set => Set(value); } + public Image? Image { get => Get(); set => Set(value); } /// /// Gets or sets the horizontal resolution to be displayed @@ -73,7 +73,7 @@ public abstract class ImageInfoBase : ValidatingObjectBase /// Gets or sets the palette color entries to be displayed /// when debugging the corresponding or instance. /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a DTO class")] + [AllowNull] public Color[] Palette { get => Get(Reflector.EmptyArray()); set => Set(value ?? Reflector.EmptyArray()); } /// @@ -129,7 +129,7 @@ protected override void Dispose(bool disposing) #region Private Protected Methods - private protected void InitMeta(Image image) + private protected void InitMeta(Image? image) { if (image == null) return; diff --git a/KGySoft.Drawing.ImagingTools/Model/InstallationInfo.cs b/KGySoft.Drawing.ImagingTools/Model/InstallationInfo.cs index fbfab06..3ad78d6 100644 --- a/KGySoft.Drawing.ImagingTools/Model/InstallationInfo.cs +++ b/KGySoft.Drawing.ImagingTools/Model/InstallationInfo.cs @@ -18,13 +18,13 @@ using System; using System.Diagnostics; +#if NETFRAMEWORK using System.Diagnostics.CodeAnalysis; +#endif using System.IO; using System.Reflection; -using System.Runtime.Versioning; - -#if NETFRAMEWORK -using KGySoft.CoreLibraries; +#if !NET35 +using System.Runtime.Versioning; #endif #if NETCOREAPP using System.Runtime.Loader; @@ -33,6 +33,10 @@ using System.Security.Policy; #endif +#if NETFRAMEWORK +using KGySoft.CoreLibraries; +#endif + #endregion namespace KGySoft.Drawing.ImagingTools.Model @@ -98,14 +102,14 @@ internal SandboxContext(string path) : base(nameof(SandboxContext), isCollectibl #region Methods - protected override Assembly Load(AssemblyName name) + protected override Assembly? Load(AssemblyName name) { // ensuring that dependencies of the main assembly are also loaded into this context - string assemblyPath = resolver.ResolveAssemblyToPath(name); + string? assemblyPath = resolver.ResolveAssemblyToPath(name); if (assemblyPath == null) return null; - using var fs = File.OpenRead(assemblyPath); + using FileStream fs = File.OpenRead(assemblyPath); return LoadFromStream(fs); } @@ -131,26 +135,32 @@ protected override Assembly Load(AssemblyName name) /// Gets the version of an identified debugger visualizer installation. /// Can return  even if is , if the installed version could not be determined. /// - public Version Version { get; private set; } + public Version? Version { get; private set; } /// /// Gets the runtime version of an identified debugger visualizer installation. /// Can return  even if is , if the runtime version could not be determined. /// - public string RuntimeVersion { get; private set; } + public string? RuntimeVersion { get; private set; } /// /// Gets the target framework of an identified debugger visualizer installation. /// Can return  even if is , if the assembly does no contain target framework information /// (typically .NET 3.5 version). /// - public string TargetFramework { get; private set; } + // ReSharper disable once UnassignedGetOnlyAutoProperty + public string? TargetFramework + { + get; +#if NET40 || NET45 + private set; +#endif + } #endregion #region Constructors - [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute", Justification = "typeof().FullName will not be null")] internal InstallationInfo(string path) { Path = path; @@ -211,7 +221,8 @@ private void InitializeInfoByFileVersion(string path) { try { - Version = new Version(FileVersionInfo.GetVersionInfo(InstallationManager.GetDebuggerVisualizerFilePath(path)).FileVersion); + string? fileVersion = FileVersionInfo.GetVersionInfo(InstallationManager.GetDebuggerVisualizerFilePath(path)).FileVersion; + Version = fileVersion == null ? null : new Version(fileVersion); } catch (Exception e) when (!e.IsCritical()) { diff --git a/KGySoft.Drawing.ImagingTools/Model/QuantizerDescriptor.cs b/KGySoft.Drawing.ImagingTools/Model/QuantizerDescriptor.cs index bfe393d..ec6ffb8 100644 --- a/KGySoft.Drawing.ImagingTools/Model/QuantizerDescriptor.cs +++ b/KGySoft.Drawing.ImagingTools/Model/QuantizerDescriptor.cs @@ -57,7 +57,12 @@ internal sealed class QuantizerDescriptor }, ["pixelFormat"] = new CustomPropertyDescriptor("pixelFormat", typeof(PixelFormat)) { - AllowedValues = Enum.GetValues().Where(pf => pf.IsValidFormat()).OrderBy(pf => pf & PixelFormat.Max).Select(pf => (object)pf).ToArray(), + AllowedValues = Enum.GetValues() + .Where(pf => pf.IsValidFormat()) + // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags + .OrderBy(pf => pf & PixelFormat.Max) + .Select(pf => (object)pf) + .ToArray(), }, ["directMapping"] = new CustomPropertyDescriptor("directMapping", typeof(bool)) { DefaultValue = false }, ["maxColors"] = new CustomPropertyDescriptor("maxColors", typeof(int)) @@ -65,7 +70,7 @@ internal sealed class QuantizerDescriptor DefaultValue = 256, AdjustValue = value => { - if (!(value is int i)) + if (value is not int i) return 0; return i < 0 ? 0 @@ -87,17 +92,17 @@ internal sealed class QuantizerDescriptor #region Constructors - internal QuantizerDescriptor(Type type, string methodName) : this(type.GetMethod(methodName)) + internal QuantizerDescriptor(Type type, string methodName) : this(type.GetMethod(methodName)!) { } internal QuantizerDescriptor(MethodInfo method) { - this.Method = method; + Method = method; ParameterInfo[] methodParams = method.GetParameters(); Parameters = new CustomPropertyDescriptor[methodParams.Length]; for (int i = 0; i < Parameters.Length; i++) - Parameters[i] = parametersMapping.GetValueOrDefault(methodParams[i].Name) ?? throw new InvalidOperationException(Res.InternalError($"Unexpected parameter: {methodParams[i].Name}")); + Parameters[i] = parametersMapping.GetValueOrDefault(methodParams[i].Name!) ?? throw new InvalidOperationException(Res.InternalError($"Unexpected parameter: {methodParams[i].Name}")); } #endregion @@ -106,15 +111,15 @@ internal QuantizerDescriptor(MethodInfo method) #region Public Methods - public override string ToString() => $"{Method.DeclaringType.Name}.{Method.Name}"; + public override string ToString() => $"{Method.DeclaringType!.Name}.{Method.Name}"; #endregion #region Internal Methods - internal object[] EvaluateParameters(CustomPropertiesObject values) + internal object?[] EvaluateParameters(CustomPropertiesObject values) { - var result = new object[Parameters.Length]; + var result = new object?[Parameters.Length]; for (int i = 0; i < result.Length; i++) result[i] = Parameters[i].GetValue(values); return result; diff --git a/KGySoft.Drawing.ImagingTools/Model/_Interfaces/ICustomPropertiesProvider.cs b/KGySoft.Drawing.ImagingTools/Model/_Interfaces/ICustomPropertiesProvider.cs index 23efedb..2467a7e 100644 --- a/KGySoft.Drawing.ImagingTools/Model/_Interfaces/ICustomPropertiesProvider.cs +++ b/KGySoft.Drawing.ImagingTools/Model/_Interfaces/ICustomPropertiesProvider.cs @@ -26,8 +26,8 @@ internal interface ICustomPropertiesProvider : ICustomTypeDescriptor { #region Methods - object GetValue(string propertyName, object defaultValue); - void SetValue(string propertyName, object value); + object? GetValue(string propertyName, object? defaultValue); + void SetValue(string propertyName, object? value); void ResetValue(string propertyName); #endregion diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 393b57b..eee7352 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -51,7 +51,7 @@ internal static class Res }; // ReSharper disable once CollectionNeverUpdated.Local - private static readonly Cache localizablePropertiesCache = new Cache(GetLocalizableProperties); + private static readonly Cache localizablePropertiesCache = new Cache(GetLocalizableProperties); #endregion @@ -239,13 +239,13 @@ internal static void ApplyResources(object target, string name) { // Unlike ComponentResourceManager we don't go by ResourceSet because that would kill resource fallback traversal // so we go by localizable properties - PropertyInfo[] properties = localizablePropertiesCache[target.GetType()]; + PropertyInfo[]? properties = localizablePropertiesCache[target.GetType()]; if (properties == null) return; foreach (PropertyInfo property in properties) { - string value = resourceManager.GetString(name + "." + property.Name, LanguageSettings.DisplayLanguage); + string? value = resourceManager.GetString(name + "." + property.Name, LanguageSettings.DisplayLanguage); if (value == null) continue; Reflector.SetProperty(target, property, value); @@ -429,10 +429,10 @@ internal static string InfoColor(int argb, string knownColors, string systemColo internal static string ErrorMessageFailedToGeneratePreview(string message) => Get("ErrorMessage_FailedToGeneratePreviewFormat", message); /// Value must be between {0} and {1} - internal static string ErrorMessageValueMustBeBetween(T low, T high) => Get("ErrorMessage_ValueMustBeBetweenFormat", low, high); + internal static string ErrorMessageValueMustBeBetween(T low, T high) where T : struct => Get("ErrorMessage_ValueMustBeBetweenFormat", low, high); /// Value must be greater than {0} - internal static string ErrorMessageValueMustBeGreaterThan(T value) => Get("ErrorMessage_ValueMustBeGreaterThanFormat", value); + internal static string ErrorMessageValueMustBeGreaterThan(T value) where T : struct => Get("ErrorMessage_ValueMustBeGreaterThanFormat", value); /// Could not create directory {0}: {1} /// @@ -516,13 +516,13 @@ internal static string InfoColor(int argb, string knownColors, string systemColo #region Private Methods - private static string Get(string id, params object[] args) + private static string Get(string id, params object?[]? args) { string format = Get(id); return args == null ? format : SafeFormat(format, args); } - private static string SafeFormat(string format, object[] args) + private static string SafeFormat(string format, object?[] args) { try { @@ -531,10 +531,7 @@ private static string SafeFormat(string format, object[] args) { string nullRef = PublicResources.Null; for (; i < args.Length; i++) - { - if (args[i] == null) - args[i] = nullRef; - } + args[i] ??= nullRef; } return String.Format(LanguageSettings.FormattingLanguage, format, args); @@ -545,7 +542,7 @@ private static string SafeFormat(string format, object[] args) } } - private static PropertyInfo[] GetLocalizableProperties(Type type) + private static PropertyInfo[]? GetLocalizableProperties(Type type) { // Getting string properties only. The resource manager in this class works in safe mode anyway. var result = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) diff --git a/KGySoft.Drawing.ImagingTools/System/Diagnostics.CodeAnalysis/Attributes.cs b/KGySoft.Drawing.ImagingTools/System/Diagnostics.CodeAnalysis/Attributes.cs new file mode 100644 index 0000000..fac5114 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/System/Diagnostics.CodeAnalysis/Attributes.cs @@ -0,0 +1,29 @@ +#if NETFRAMEWORK || NETCOREAPP2_0 +// ReSharper disable once CheckNamespace +namespace System.Diagnostics.CodeAnalysis +{ + /// Specifies that null is allowed as an input even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + internal sealed class AllowNullAttribute : Attribute { } + + /// Specifies that an output may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class MaybeNullAttribute : Attribute { } + + /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class MaybeNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + // ReSharper disable once UnusedAutoPropertyAccessor.Global - used by the compiler + public bool ReturnValue { get; } + } +} + +#endif \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/IsExternalInit.cs b/KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/IsExternalInit.cs new file mode 100644 index 0000000..7f69684 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/IsExternalInit.cs @@ -0,0 +1,8 @@ +#if NETFRAMEWORK +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit { } +} + +#endif \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs index 4fc0c56..eb52441 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs @@ -76,8 +76,8 @@ public bool Checked #region Constructors - [SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Whitespace")] [SuppressMessage("ReSharper", "LocalizableElement", Justification = "Whitespace")] + [SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "ReSharper issue")] public CheckGroupBox() { InitializeComponent(); @@ -96,7 +96,7 @@ public CheckGroupBox() // GroupBox.FlayStyle must be the same as CheckBox; otherwise, System appearance would be transparent FlatStyle = checkBox.FlatStyle; - checkBox.CheckedChanged += this.CheckBox_CheckedChanged; + checkBox.CheckedChanged += CheckBox_CheckedChanged; // making sure there is enough space before the CheckBox at every DPI base.Text = " "; @@ -150,7 +150,7 @@ protected override void Dispose(bool disposing) #region Event handlers - private void CheckBox_CheckedChanged(object sender, EventArgs e) + private void CheckBox_CheckedChanged(object? sender, EventArgs e) { // Toggling the Enabled state of the content. This method preserves the original Enabled state of the controls. contentPanel.Enabled = checkBox.Checked; diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs index 4906d61..f77c9cc 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs @@ -36,8 +36,8 @@ internal partial class DrawingProgressStatusStrip : StatusStrip #region Properties - [SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Whitespace")] [SuppressMessage("ReSharper", "LocalizableElement", Justification = "Whitespace")] + [SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "ReSharper issue")] internal bool ProgressVisible { get => progressVisible; @@ -145,11 +145,11 @@ private void UpdateProgress(DrawingProgress progress) #region Event handlers #pragma warning disable IDE1006 // Naming Styles - private void lblProgress_TextChanged(object sender, EventArgs e) => AdjustSize(); - private void lblProgress_VisibleChanged(object sender, EventArgs e) => AdjustSize(); - private void DrawingProgressStatusStrip_SizeChanged(object sender, EventArgs e) => AdjustSize(); + private void lblProgress_TextChanged(object? sender, EventArgs e) => AdjustSize(); + private void lblProgress_VisibleChanged(object? sender, EventArgs e) => AdjustSize(); + private void DrawingProgressStatusStrip_SizeChanged(object? sender, EventArgs e) => AdjustSize(); - private void timer_Tick(object sender, EventArgs e) => UpdateProgress(Progress); + private void timer_Tick(object? sender, EventArgs e) => UpdateProgress(Progress); #pragma warning restore IDE1006 // Naming Styles #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 33b4f32..9518562 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -18,7 +18,6 @@ using System; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; @@ -64,7 +63,7 @@ private sealed class GenerateTask : AsyncTaskBase { #region Fields - internal Image SourceImage; + internal Image? SourceImage; internal Size Size; #endregion @@ -78,16 +77,15 @@ private sealed class GenerateTask : AsyncTaskBase private readonly object syncRootGenerate = new object(); private bool enabled; - private GenerateTask activeTask; + private GenerateTask? activeTask; - private Image sourceClone; - private Image safeDefaultImage; // The default image displayed when no generated preview is needed or while generation is in progress + private Image? sourceClone; + private Image? safeDefaultImage; // The default image displayed when no generated preview is needed or while generation is in progress private bool isClonedSafeDefaultImage; private Size requestedSize; - [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "False alarm, it is either equals safeDefaultImage or currentPreview")] - private volatile Image displayImage; // The actual displayed image. If not null, it is either equals safeDefaultImage or currentPreview. - private volatile Bitmap cachedDisplayImage; // The lastly generated display image. Can be unused but is cached until a next preview is generated. + private volatile Image? displayImage; // The actual displayed image. If not null, it is either equals safeDefaultImage or currentPreview. + private volatile Bitmap? cachedDisplayImage; // The lastly generated display image. Can be unused but is cached until a next preview is generated. private Size currentCachedDisplayImage; // just to cache cachedDisplayImage.Size, because accessing currentPreview can lead to "object is used elsewhere" error #endregion @@ -113,17 +111,16 @@ internal PreviewGenerator(ImageViewer owner) #region Internal Methods - [SuppressMessage("Reliability", "CA2002:Do not lock on objects with weak identity", Justification = "False alarm, image is not a remote object")] - internal Image GetDisplayImage(bool generateSyncIfNull) + internal Image? GetDisplayImage(bool generateSyncIfNull) { - Image result = displayImage; + Image? result = displayImage; if (result != null || !generateSyncIfNull) return result; if (safeDefaultImage == null) { - Image image = owner.image; - Debug.Assert(image != null, "Image is not expected to be null here"); + Debug.Assert(owner.image != null, "Image is not expected to be null here"); + Image image = owner.image!; PixelFormat pixelFormat = image.PixelFormat; try @@ -161,18 +158,18 @@ internal Image GetDisplayImage(bool generateSyncIfNull) } // it is possible that we have a displayImage now but if not we return the default - return displayImage ??= safeDefaultImage; + if (displayImage == null) + Interlocked.CompareExchange(ref displayImage, safeDefaultImage, null); + return displayImage; } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "False alarm, task is passed to DoGenerate")] - [SuppressMessage("Reliability", "CA2002:Do not lock on objects with weak identity", Justification = "False alarm, image is not a remote object")] internal void BeginGenerateDisplayImage() { CancelRunningGenerate(); if (!enabled) return; - Image image = owner.image; + Image? image = owner.image; if (image == null) { Debug.Assert(cachedDisplayImage == null && displayImage == null); @@ -191,7 +188,7 @@ internal void BeginGenerateDisplayImage() } requestedSize = size; - ThreadPool.QueueUserWorkItem(DoGenerate, new GenerateTask { SourceImage = sourceClone, Size = size }); + ThreadPool.QueueUserWorkItem(DoGenerate!, new GenerateTask { SourceImage = sourceClone, Size = size }); } internal void Free() @@ -216,7 +213,7 @@ internal void Free() private void CancelRunningGenerate() { - GenerateTask runningTask = activeTask; + GenerateTask? runningTask = activeTask; if (runningTask == null) return; runningTask.IsCanceled = true; @@ -225,7 +222,7 @@ private void CancelRunningGenerate() private void WaitForPendingGenerate() { // In a non-UI thread it should be in a lock - GenerateTask runningTask = activeTask; + GenerateTask? runningTask = activeTask; if (runningTask == null) return; runningTask.WaitForCompletion(); @@ -233,7 +230,7 @@ private void WaitForPendingGenerate() activeTask = null; } - private bool TrySetPreview(Image reference, Size size) + private bool TrySetPreview(Image? reference, Size size) { if (sourceClone != null && reference != sourceClone) { @@ -255,7 +252,6 @@ private bool TrySetPreview(Image reference, Size size) return true; } - [SuppressMessage("Reliability", "CA2002:Do not lock on objects with weak identity", Justification = "False alarm, this is not a remote object and is not exposed publicly")] private void FreeCachedPreview() { lock (this) // It is alright, this is a private class. ImageViewer also locks on this instance when obtains display image so this ensures that no disposed image is painted. @@ -266,14 +262,13 @@ private void FreeCachedPreview() owner.Invalidate(); } - Bitmap toFree = cachedDisplayImage; + Bitmap? toFree = cachedDisplayImage; cachedDisplayImage = null; toFree?.Dispose(); currentCachedDisplayImage = default; } } - [SuppressMessage("Reliability", "CA2002:Do not lock on objects with weak identity", Justification = "False alarm, task.ReferenceImage is not a remote object")] private void DoGenerate(object state) { var task = (GenerateTask)state; @@ -304,7 +299,7 @@ private void DoGenerate(object state) if (task.SourceImage == null) { Debug.Assert(sourceClone == null && owner.image != null); - Image image = owner.image; + Image image = owner.image!; // As OnPaint can occur any time in the UI thread we lock on it. See also PaintImage. lock (image) @@ -351,7 +346,7 @@ private void DoGenerate(object state) try { - Bitmap result = null; + Bitmap? result = null; try { if (!task.IsCanceled) @@ -387,7 +382,7 @@ private void DoGenerate(object state) } } - private static Bitmap GenerateMetafilePreview(GenerateTask task) + private static Bitmap? GenerateMetafilePreview(GenerateTask task) { // For the resizing large managed buffer of source.Height * target.Width of ColorF (16 bytes) is allocated internally. To be safe we count with the doubled sizes. Size doubledSize = new Size(task.Size.Width << 1, task.Size.Height << 1); @@ -402,11 +397,11 @@ private static Bitmap GenerateMetafilePreview(GenerateTask task) return null; // MetafileExtensions.ToBitmap does the same if anti aliasing is requested but this way the process can be canceled Debug.WriteLine($"Generating anti aliased image {task.Size.Width}x{task.Size.Height} on thread #{Thread.CurrentThread.ManagedThreadId}"); - Bitmap result = null; - Bitmap doubled = null; + Bitmap? result = null; + Bitmap? doubled = null; try { - doubled = new Bitmap(task.SourceImage, task.Size.Width << 1, task.Size.Height << 1); + doubled = new Bitmap(task.SourceImage!, task.Size.Width << 1, task.Size.Height << 1); if (!task.IsCanceled) { result = new Bitmap(task.Size.Width, task.Size.Height, PixelFormat.Format32bppPArgb); @@ -442,22 +437,22 @@ private static Bitmap GenerateMetafilePreview(GenerateTask task) return result; } - private static Bitmap GenerateBitmapPreview(GenerateTask task) + private static Bitmap? GenerateBitmapPreview(GenerateTask task) { // BitmapExtensions.Resize does the same but this way the process can be canceled Debug.WriteLine($"Generating smoothed image {task.Size.Width}x{task.Size.Height} on thread #{Thread.CurrentThread.ManagedThreadId}"); - Bitmap result = null; + Bitmap? result = null; try { result = new Bitmap(task.Size.Width, task.Size.Height, PixelFormat.Format32bppPArgb); - using IReadableBitmapData src = ((Bitmap)task.SourceImage).GetReadableBitmapData(); + using IReadableBitmapData src = ((Bitmap)task.SourceImage!).GetReadableBitmapData(); using IReadWriteBitmapData dst = result.GetReadWriteBitmapData(); var cfg = new AsyncConfig { IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false, MaxDegreeOfParallelism = Environment.ProcessorCount >> 1 }; // Not using Task and await, because this method's signature must match the WaitCallback delegate, and we want to be compatible with .NET 3.5, too. // As we are already on a pool thread the End... call does not block the UI. - var srcRect = new Rectangle(Point.Empty, task.SourceImage.Size); + var srcRect = new Rectangle(Point.Empty, task.SourceImage!.Size); var dstRect = new Rectangle(Point.Empty, task.Size); if (srcRect == dstRect) { @@ -522,7 +517,7 @@ private static Bitmap GenerateBitmapPreview(GenerateTask task) private readonly PreviewGenerator previewGenerator; - private Image image; + private Image? image; private Rectangle targetRectangle; private Rectangle clientRectangle; private bool smoothZooming; @@ -543,7 +538,7 @@ private static Bitmap GenerateBitmapPreview(GenerateTask task) #region Events - internal event EventHandler ZoomChanged + internal event EventHandler? ZoomChanged { add => Events.AddHandler(nameof(ZoomChanged), value); remove => Events.RemoveHandler(nameof(ZoomChanged), value); @@ -555,7 +550,7 @@ internal event EventHandler ZoomChanged #region Internal Properties - internal Image Image + internal Image? Image { get => image; set @@ -760,7 +755,7 @@ internal void UpdateImage() #region Private Methods - private void SetImage(Image value) + private void SetImage(Image? value) { previewGenerator.Free(); image = value; @@ -915,7 +910,6 @@ private void AdjustSizes() targetRectangle = new Rectangle(targetLocation, scaledSize); } - [SuppressMessage("Reliability", "CA2002:Do not lock on objects with weak identity", Justification = "False alarm, image is not a remote object")] private void PaintImage(Graphics g) { g.IntersectClip(clientRectangle); @@ -932,7 +926,7 @@ private void PaintImage(Graphics g) // Locking on display image so if it is the same as the original image, which is also locked when accessing its bitmap data // the "bitmap region is already locked" can be avoided. Important: this cannot be ensured without locking here internally because // OnPaint can occur any time after invalidating. - Image toDraw = previewGenerator.GetDisplayImage(true); + Image toDraw = previewGenerator.GetDisplayImage(true)!; bool useLock = image == toDraw; if (useLock) Monitor.Enter(toDraw); @@ -1017,7 +1011,7 @@ private void SetZoom(float value) #region Event handlers - private void ScrollbarValueChanged(object sender, EventArgs e) => Invalidate(); + private void ScrollbarValueChanged(object? sender, EventArgs e) => Invalidate(); #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/NotificationLabel.cs b/KGySoft.Drawing.ImagingTools/View/Controls/NotificationLabel.cs index 4d0eb91..1c398f7 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/NotificationLabel.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/NotificationLabel.cs @@ -18,7 +18,6 @@ using System; using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms; @@ -55,7 +54,7 @@ public override string Text } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public new Image Image + public new Image? Image { get => base.Image; set @@ -98,8 +97,6 @@ public NotificationLabel() #region Public Methods - [SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", - Justification = "Font measuring must not rely on a variable resource text")] public override Size GetPreferredSize(Size proposedSize) { // Workaround: Immediately after calculating preferred size (eg. Dock == Top), another request arrives with empty proposedSize, which ruins the constrained result. diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs index 1249341..84938ce 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs @@ -45,7 +45,7 @@ internal sealed partial class PalettePanel : BaseControl #region Instance Fields - private IList palette; + private IList? palette; private int selectedColorIndex = -1; private int firstVisibleColor; private int visibleRowCount; @@ -59,7 +59,7 @@ internal sealed partial class PalettePanel : BaseControl #region Events - internal event EventHandler SelectedColorChanged + internal event EventHandler? SelectedColorChanged { add => Events.AddHandler(nameof(SelectedColorChanged), value); remove => Events.RemoveHandler(nameof(SelectedColorChanged), value); @@ -71,7 +71,7 @@ internal event EventHandler SelectedColorChanged #region Internal Properties - internal IList Palette + internal IList? Palette { set { @@ -194,7 +194,7 @@ protected override void OnPaint(PaintEventArgs e) return; e.Graphics.PixelOffsetMode = PixelOffsetMode.Half; - int upper = Math.Min(palette.Count, firstVisibleColor + (visibleRowCount << 4)); + int upper = Math.Min(palette!.Count, firstVisibleColor + (visibleRowCount << 4)); // iterating through visible colors for (int i = firstVisibleColor; i < upper; i++) @@ -366,7 +366,7 @@ private bool CheckPaletteLayout() Invalidate(); visibleRowCount = maxRows; - int colorRows = (int)Math.Ceiling((double)palette.Count / 16); + int colorRows = (int)Math.Ceiling((double)palette!.Count / 16); if (visibleRowCount >= colorRows) { // scrollbar is not needed @@ -408,14 +408,14 @@ private bool IsSelectedColorVisible() //ReSharper disable InconsistentNaming #pragma warning disable IDE1006 // Naming Styles - private void sbPalette_ValueChanged(object sender, EventArgs e) + private void sbPalette_ValueChanged(object? sender, EventArgs e) { firstVisibleColor = sbPalette.Value << 4; timerSelection.Enabled = IsSelectedColorVisible(); Invalidate(); } - private void timerSelection_Tick(object sender, EventArgs e) + private void timerSelection_Tick(object? sender, EventArgs e) { if (!IsSelectedColorVisible()) { diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingCheckBox.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingCheckBox.cs index 081fd96..b7bd8ca 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingCheckBox.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingCheckBox.cs @@ -1,11 +1,26 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: ScalingCheckBox.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2020 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + using System.Drawing; -using System.Linq; -using System.Text; using System.Windows.Forms; +#endregion + namespace KGySoft.Drawing.ImagingTools.View.Controls { /// @@ -13,6 +28,8 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls /// internal class ScalingCheckBox : CheckBox { + #region Methods + public override Size GetPreferredSize(Size proposedSize) { var flatStyle = FlatStyle; @@ -34,5 +51,7 @@ public override Size GetPreferredSize(Size proposedSize) ResumeLayout(); return result; } + + #endregion } -} +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs index 2f051f5..81aa0c8 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs @@ -17,7 +17,6 @@ #region Usings using System; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; @@ -93,7 +92,6 @@ protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) } } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "False alarm, see the disposing at the end")] protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) { Rectangle imageRect = e.ImageRectangle; diff --git a/KGySoft.Drawing.ImagingTools/View/Design/DithererStrengthEditor.cs b/KGySoft.Drawing.ImagingTools/View/Design/DithererStrengthEditor.cs index fe9a702..e829b2a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Design/DithererStrengthEditor.cs +++ b/KGySoft.Drawing.ImagingTools/View/Design/DithererStrengthEditor.cs @@ -42,11 +42,11 @@ internal class DithererStrengthEditor : UITypeEditor public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.DropDown; - public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) + public override object? EditValue(ITypeDescriptorContext? context, IServiceProvider? provider, object? value) { if (provider == null || value == null) return value; - IWindowsFormsEditorService editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); + var editorService = (IWindowsFormsEditorService?)provider.GetService(typeof(IWindowsFormsEditorService)); if (editorService == null) return value; diff --git a/KGySoft.Drawing.ImagingTools/View/Design/QuantizerThresholdEditor.cs b/KGySoft.Drawing.ImagingTools/View/Design/QuantizerThresholdEditor.cs index bf9c385..b79ba53 100644 --- a/KGySoft.Drawing.ImagingTools/View/Design/QuantizerThresholdEditor.cs +++ b/KGySoft.Drawing.ImagingTools/View/Design/QuantizerThresholdEditor.cs @@ -42,11 +42,11 @@ internal class QuantizerThresholdEditor : UITypeEditor public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.DropDown; - public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) + public override object? EditValue(ITypeDescriptorContext context, IServiceProvider? provider, object? value) { if (provider == null || value == null) return value; - IWindowsFormsEditorService editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); + var editorService = (IWindowsFormsEditorService?)provider.GetService(typeof(IWindowsFormsEditorService)); if (editorService == null) return value; diff --git a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs index eff06a7..9d66992 100644 --- a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs +++ b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs @@ -16,7 +16,6 @@ #region Usings -using System; using System.Windows.Forms; #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustBrightnessForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustBrightnessForm.cs index e92832d..0fa7e5e 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustBrightnessForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustBrightnessForm.cs @@ -38,7 +38,7 @@ internal AdjustBrightnessForm(AdjustBrightnessViewModel viewModel) #region Private Constructors - private AdjustBrightnessForm() : this(null) + private AdjustBrightnessForm() : this(null!) { // this ctor is just for the designer } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.cs index 6da23d3..d6b08a9 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.cs @@ -48,7 +48,7 @@ protected AdjustColorsFormBase(AdjustColorsViewModelBase viewModel) #region Private Constructors - private AdjustColorsFormBase() : this(null) + private AdjustColorsFormBase() : this(null!) { // this ctor is just for the designer } @@ -101,26 +101,26 @@ private void InitPropertyBindings() // VM.ColorChannels <-> chbRed.Checked CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.ColorChannels), chbRed, nameof(chbRed.Checked), - channels => ((ColorChannels)channels).HasFlag(ColorChannels.R), + channels => ((ColorChannels)channels!).HasFlag(ColorChannels.R), flag => flag is true ? VM.ColorChannels | ColorChannels.R : VM.ColorChannels & ~ColorChannels.R); // VM.ColorChannels <-> chbGreen.Checked CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.ColorChannels), chbGreen, nameof(chbGreen.Checked), - channels => ((ColorChannels)channels).HasFlag(ColorChannels.G), + channels => ((ColorChannels)channels!).HasFlag(ColorChannels.G), flag => flag is true ? VM.ColorChannels | ColorChannels.G : VM.ColorChannels & ~ColorChannels.G); // VM.ColorChannels <-> chbBlue.Checked CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.ColorChannels), chbBlue, nameof(chbBlue.Checked), - channels => ((ColorChannels)channels).HasFlag(ColorChannels.B), + channels => ((ColorChannels)channels!).HasFlag(ColorChannels.B), flag => flag is true ? VM.ColorChannels | ColorChannels.B : VM.ColorChannels & ~ColorChannels.B); // VM.Value <-> trackBar.Value CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.Value), trackBar, nameof(trackBar.Value), - value => (int)((float)value * 100), - value => (int)value / 100f); + value => (int)((float)value! * 100), + value => (int)value! / 100f); // VM.Value -> lblValue.Text - CommandBindings.AddPropertyBinding(ViewModel, nameof(VM.Value), nameof(lblValue.Text), v => ((float)v).ToString("F2", CultureInfo.CurrentCulture), lblValue); + CommandBindings.AddPropertyBinding(ViewModel, nameof(VM.Value), nameof(lblValue.Text), v => ((float)v!).ToString("F2", CultureInfo.CurrentCulture), lblValue); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustContrastForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustContrastForm.cs index d7da903..ee96a91 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustContrastForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustContrastForm.cs @@ -38,7 +38,7 @@ internal AdjustContrastForm(AdjustContrastViewModel viewModel) #region Private Constructors - private AdjustContrastForm() : this(null) + private AdjustContrastForm() : this(null!) { // this ctor is just for the designer } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustGammaForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustGammaForm.cs index 5359489..073e398 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustGammaForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustGammaForm.cs @@ -38,7 +38,7 @@ internal AdjustGammaForm(AdjustGammaViewModel viewModel) #region Private Constructors - private AdjustGammaForm() : this(null) + private AdjustGammaForm() : this(null!) { // this ctor is just for the designer } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs index 2156c4a..37fe89d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs @@ -17,10 +17,10 @@ #region Usings using System; -using System.Drawing; -using System.Drawing.Imaging; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Windows.Forms; + using KGySoft.Drawing.ImagingTools.ViewModel; #endregion @@ -31,7 +31,7 @@ internal partial class AppMainForm : ImageVisualizerForm { #region Fields - private static readonly string title = Res.TitleAppNameAndVersion(typeof(Res).Assembly.GetName().Version); + private static readonly string title = Res.TitleAppNameAndVersion(typeof(Res).Assembly.GetName().Version!); #endregion @@ -39,6 +39,7 @@ internal partial class AppMainForm : ImageVisualizerForm #region Public Properties + [AllowNull] public override string Text { // base has VM.TitleCaption -> Text binding so this solution makes possible to enrich it in a compatible way @@ -70,7 +71,7 @@ internal AppMainForm(DefaultViewModel viewModel) #region Private Constructors - private AppMainForm() : this(null) + private AppMainForm() : this(null!) { // this ctor is just for the designer } @@ -115,9 +116,9 @@ private void InitPropertyBindings() nameof(ViewModel.FileName), nameof(ViewModel.IsModified)); } - private string FormatText(string value) + private string FormatText(string? value) { - string fileName = ViewModel.FileName; + string? fileName = ViewModel.FileName; string name = fileName == null ? Res.TextUnnamed : Path.GetFileName(fileName); return String.IsNullOrEmpty(value) ? title : $"{title} [{name}{(ViewModel.IsModified ? "*" : String.Empty)}] - {value}"; } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs index 3e8da89..4245ee5 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs @@ -17,10 +17,12 @@ #region Usings using System; +#if !NET5_0_OR_GREATER using System.Collections.Specialized; using System.Drawing; using System.Reflection; using System.Runtime.InteropServices; +#endif using System.Windows.Forms; using KGySoft.Reflection; @@ -36,6 +38,7 @@ internal class BaseForm : Form { #region NativeMethods class +#if !NET5_0_OR_GREATER private static class NativeMethods { #region Methods @@ -45,25 +48,36 @@ private static class NativeMethods #endregion } +#endif #endregion #region Constants +#if !NET5_0_OR_GREATER + // ReSharper disable once InconsistentNaming private const int WM_NCHITTEST = 0x0084; +#endif #endregion #region Fields - private static readonly BitVector32.Section formStateRenderSizeGrip = OSUtils.IsWindows ? (BitVector32.Section)Reflector.GetField(typeof(Form), "FormStateRenderSizeGrip") : default; - private static FieldAccessor formStateField; +#if !NET5_0_OR_GREATER + private static readonly BitVector32.Section formStateRenderSizeGrip = OSUtils.IsWindows + ? (BitVector32.Section)Reflector.GetField(typeof(Form), "FormStateRenderSizeGrip")! + : default; + private static FieldAccessor? formStateField; +#endif #endregion #region Properties - private BitVector32 FormState => (BitVector32)(formStateField ??= FieldAccessor.GetAccessor(typeof(Form).GetField("formState", BindingFlags.Instance | BindingFlags.NonPublic))).Get(this); +#if !NET5_0_OR_GREATER + private BitVector32 FormState => (BitVector32)(formStateField ??= + FieldAccessor.GetAccessor(typeof(Form).GetField("formState", BindingFlags.Instance | BindingFlags.NonPublic)!)).Get(this)!; +#endif #endregion @@ -71,7 +85,7 @@ private static class NativeMethods static BaseForm() { - Type dpiHelper = Reflector.ResolveType(typeof(Form).Assembly, "System.Windows.Forms.DpiHelper"); + Type? dpiHelper = Reflector.ResolveType(typeof(Form).Assembly, "System.Windows.Forms.DpiHelper"); if (dpiHelper == null) return; @@ -87,6 +101,7 @@ static BaseForm() #region Protected Methods +#if !NET5_0_OR_GREATER protected override void WndProc(ref Message m) { if (!OSUtils.IsWindows) @@ -105,6 +120,7 @@ protected override void WndProc(ref Message m) break; } } +#endif protected override void Dispose(bool disposing) { @@ -117,9 +133,10 @@ protected override void Dispose(bool disposing) #region Private Methods +#if !NET5_0_OR_GREATER /// /// Bugfix: When size grip is visible, and form is above and left of the primary monitor, form cannot be dragged anymore due to forced diagonal resizing. - /// Note: will be fixed in .NET Core (see also https://github.com/dotnet/winforms/issues/1504) + /// Note: Needed only below .NET 5.0 because I fixed this directly in WinForms repository: https://github.com/dotnet/winforms/pull/2032/commits /// private void WmNCHitTest(ref Message m) { @@ -144,6 +161,7 @@ private void WmNCHitTest(ref Message m) m.Result = (IntPtr)18; } } +#endif #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.cs index 491157c..bb06b62 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.cs @@ -58,7 +58,7 @@ internal ColorSpaceForm(ColorSpaceViewModel viewModel) #region Private Constructors - private ColorSpaceForm() : this(null) + private ColorSpaceForm() : this(null!) { // this ctor is just for the designer } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs index 9891f66..ae2f3f7 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs @@ -39,7 +39,7 @@ internal ColorVisualizerForm(ColorVisualizerViewModel viewModel) #region Private Constructors - private ColorVisualizerForm() : this(null) + private ColorVisualizerForm() : this(null!) { // this ctor is just for the designer } @@ -84,7 +84,7 @@ private void InitPropertyBindings() // VM.Color -> ucColorVisualizer.Color, Text CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Color), nameof(ucColorVisualizer.Color), ucColorVisualizer); - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Color), nameof(Text), c => Res.TitleColor((Color)c), this); + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Color), nameof(Text), c => Res.TitleColor((Color)c!), this); } private void InitCommandBindings() diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.cs index 455844d..7a61dc9 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.cs @@ -16,7 +16,6 @@ #region Usings -using System; using System.Windows.Forms; using KGySoft.Drawing.ImagingTools.ViewModel; @@ -42,7 +41,7 @@ internal CountColorsForm(CountColorsViewModel viewModel) #region Private Constructors - private CountColorsForm() : this(null) + private CountColorsForm() : this(null!) { // this ctor is just for the designer } @@ -96,12 +95,15 @@ private void InitPropertyBindings() // VM.DisplayText <-> lblResult.Text CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.DisplayText), nameof(lblResult.Text), lblResult); - // VM.IsProcessing -> progress.ProgressVisible - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.IsProcessing), nameof(progress.ProgressVisible), progress); - - // VM.Progress -> progress.Progress (in lock because it is already running) + // in lock because it is already running lock (ViewModel.ProgressSyncRoot) + { + // VM.IsProcessing -> progress.ProgressVisible + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.IsProcessing), nameof(progress.ProgressVisible), progress); + + // VM.Progress -> progress.Progress CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Progress), nameof(progress.Progress), progress); + } } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/GraphicsVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/GraphicsVisualizerForm.cs index 5f949dc..5d4f673 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/GraphicsVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/GraphicsVisualizerForm.cs @@ -16,7 +16,6 @@ #region Usings -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Windows.Forms; @@ -30,12 +29,7 @@ internal partial class GraphicsVisualizerForm : ImageVisualizerForm { #region Fields - [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", - Justification = "False alarm, added to tsMenu, which is disposed by base")] private readonly ToolStripButton btnCrop; - - [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", - Justification = "False alarm, added to tsMenu, which is disposed by base")] private readonly ToolStripButton btnHighlightClip; #endregion @@ -77,7 +71,7 @@ internal GraphicsVisualizerForm(GraphicsVisualizerViewModel viewModel) #region Private Constructors - private GraphicsVisualizerForm() : this(null) + private GraphicsVisualizerForm() : this(null!) { // this ctor is just for the designer } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index b0a45ec..989288c 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -44,7 +44,7 @@ internal ImageVisualizerForm(ImageVisualizerViewModel viewModel) #region Private Constructors - private ImageVisualizerForm() : this(null) + private ImageVisualizerForm() : this(null!) { // this ctor is just for the designer } @@ -291,14 +291,14 @@ private void InitCommandBindings() private Rectangle GetScreenRectangle() => Screen.FromHandle(Handle).WorkingArea; - private string SelectFileToOpen() + private string? SelectFileToOpen() { if (dlgOpen.ShowDialog(this) != DialogResult.OK) return null; return dlgOpen.FileName; } - private string SelectFileToSave() + private string? SelectFileToSave() { if (dlgSave.ShowDialog(this) != DialogResult.OK) return null; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs index 75987ac..73fed3f 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs @@ -43,7 +43,7 @@ internal ManageInstallationsForm(ManageInstallationsViewModel viewModel) #region Private Constructors - private ManageInstallationsForm() : this(null) + private ManageInstallationsForm() : this(null!) { // this ctor is just for the designer } @@ -114,7 +114,7 @@ private void InitCommandBindings() .AddSource(btnRemove, nameof(btnRemove.Click)); } - private string SelectFolder() + private string? SelectFolder() { using (var dlg = new FolderBrowserDialog { SelectedPath = ViewModel.CurrentPath }) return dlg.ShowDialog() != DialogResult.OK ? null : dlg.SelectedPath; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index cca6e0f..d83359f 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -39,8 +39,8 @@ internal class MvvmBaseForm : BaseForm, IView #region Protected Properties - protected TViewModel ViewModel { get; } - protected CommandBindingsCollection CommandBindings { get; } + protected TViewModel ViewModel { get; } = default!; + protected CommandBindingsCollection CommandBindings { get; } = new WinFormsCommandBindingsCollection(); #endregion @@ -60,8 +60,9 @@ internal class MvvmBaseForm : BaseForm, IView protected MvvmBaseForm(TViewModel viewModel) { // occurs in design mode but DesignMode is false for grandchild forms - if (viewModel == null) + if (viewModel == null!) return; + ViewModel = viewModel; ViewModelBase vm = VM; @@ -72,8 +73,6 @@ protected MvvmBaseForm(TViewModel viewModel) vm.ShowChildViewCallback = ShowChildView; vm.CloseViewCallback = () => BeginInvoke(new Action(Close)); vm.SynchronizedInvokeCallback = InvokeIfRequired; - - CommandBindings = new WinformsCommandBindingsCollection(); } #endregion @@ -96,8 +95,11 @@ private MvvmBaseForm() protected override void OnLoad(EventArgs e) { base.OnLoad(e); - if (ViewModel == null) + + // occurs in design mode but DesignMode is false for grandchild forms + if (ViewModel == null!) return; + ApplyResources(); ApplyViewModel(); } @@ -116,7 +118,7 @@ protected override void OnFormClosing(FormClosingEventArgs e) protected override void Dispose(bool disposing) { if (disposing) - CommandBindings?.Dispose(); + CommandBindings.Dispose(); base.Dispose(disposing); } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs index 162c3e4..0cb5add 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs @@ -38,7 +38,7 @@ internal PaletteVisualizerForm(PaletteVisualizerViewModel viewModel) #region Private Constructors - private PaletteVisualizerForm() : this(null) + private PaletteVisualizerForm() : this(null!) { // this ctor is just for the designer } @@ -82,7 +82,7 @@ private void InitPropertyBindings() CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Palette), nameof(pnlPalette.Palette), pnlPalette); // VM.Count -> Text (formatted) - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Count), nameof(Text), c => Res.TitlePaletteCount((int)c), this); + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Count), nameof(Text), c => Res.TitlePaletteCount((int)c!), this); // VM.ReadOnly -> ucColorVisualizer.ReadOnly CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.ReadOnly), nameof(ucColorVisualizer.ReadOnly), ucColorVisualizer); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs index 9cbb03e..590ec33 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs @@ -50,7 +50,7 @@ internal ResizeBitmapForm(ResizeBitmapViewModel viewModel) #region Private Constructors - private ResizeBitmapForm() : this(null) + private ResizeBitmapForm() : this(null!) { // this ctor is just for the designer } @@ -121,19 +121,19 @@ private void InitPropertyBindings() // VM.WidthRatio <-> txtWidthPercent.Text CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.WidthRatio), txtWidthPercent, nameof(txtWidthPercent.Text), - FormatPercentage, ParsePercentage); + FormatPercentage!, ParsePercentage!); // VM.HeightRatio <-> txtHeightPercent.Text CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.HeightRatio), txtHeightPercent, nameof(txtHeightPercent.Text), - FormatPercentage, ParsePercentage); + FormatPercentage!, ParsePercentage!); // VM.Width <-> txtWidthPx.Text CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.Width), txtWidthPx, nameof(txtWidthPx.Text), - FormatInteger, ParseInteger); + FormatInteger!, ParseInteger!); // VM.Height <-> txtHeightPx.Text CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.Height), txtHeightPx, nameof(txtHeightPx.Text), - FormatInteger, ParseInteger); + FormatInteger!, ParseInteger!); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs index 09d6f55..aae5e31 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs @@ -61,7 +61,7 @@ protected TransformBitmapFormBase(TransformBitmapViewModelBase viewModel) #region Private Constructors - private TransformBitmapFormBase() : this(null) + private TransformBitmapFormBase() : this(null!) { // this ctor is just for the designer } @@ -154,9 +154,9 @@ private void OnValidationResultsChangedCommand(ICommandSource mapping in ValidationMapping) { var validationResults = src.EventArgs.EventData[mapping.Key]; - ValidationResult error = validationResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Error); - ValidationResult warning = error == null ? validationResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Warning) : null; - ValidationResult info = error == null && warning == null ? validationResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Information) : null; + ValidationResult? error = validationResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Error); + ValidationResult? warning = error == null ? validationResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Warning) : null; + ValidationResult? info = error == null && warning == null ? validationResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Information) : null; errorProvider.SetError(mapping.Value, error?.Message); warningProvider.SetError(mapping.Value, warning?.Message); infoProvider.SetError(mapping.Value, info?.Message); diff --git a/KGySoft.Drawing.ImagingTools/View/Images.cs b/KGySoft.Drawing.ImagingTools/View/Images.cs index f3034b5..85695d1 100644 --- a/KGySoft.Drawing.ImagingTools/View/Images.cs +++ b/KGySoft.Drawing.ImagingTools/View/Images.cs @@ -31,28 +31,28 @@ internal static class Images #region Private Fields - private static Bitmap check; - private static Bitmap crop; - private static Bitmap highlightVisibleClip; - private static Bitmap magnifier; - private static Bitmap save; - private static Bitmap open; - private static Bitmap clear; - private static Bitmap prev; - private static Bitmap next; - private static Bitmap palette; - private static Bitmap settings; - private static Bitmap animation; - private static Bitmap multiSize; - private static Bitmap multiPage; - private static Bitmap smoothZoom; - private static Bitmap edit; - private static Bitmap rotateLeft; - private static Bitmap rotateRight; - private static Bitmap resize; - private static Bitmap quantize; - private static Bitmap colors; - private static Bitmap compare; + private static Bitmap? check; + private static Bitmap? crop; + private static Bitmap? highlightVisibleClip; + private static Bitmap? magnifier; + private static Bitmap? save; + private static Bitmap? open; + private static Bitmap? clear; + private static Bitmap? prev; + private static Bitmap? next; + private static Bitmap? palette; + private static Bitmap? settings; + private static Bitmap? animation; + private static Bitmap? multiSize; + private static Bitmap? multiPage; + private static Bitmap? smoothZoom; + private static Bitmap? edit; + private static Bitmap? rotateLeft; + private static Bitmap? rotateRight; + private static Bitmap? resize; + private static Bitmap? quantize; + private static Bitmap? colors; + private static Bitmap? compare; #endregion @@ -115,7 +115,7 @@ internal static Icon ToScaledIcon(this Icon icon, bool legacyScaling = true) // Windows XP-Windows 7 with legacy scaling: we need to make sure that icon size is dividable by 16 // so it will not be corrupted (eg. ErrorProvider) - using Bitmap iconImage = icon.ExtractBitmap(result.Size); + using Bitmap iconImage = icon.ExtractBitmap(result.Size)!; result.Dispose(); // returning a larger icon without scaling so apparently it will have the same size as the original one @@ -128,7 +128,7 @@ internal static Icon ToScaledIcon(this Icon icon, bool legacyScaling = true) private static Bitmap GetResource(string resourceName) { - var icon = (Icon)Properties.Resources.ResourceManager.GetObject(resourceName, CultureInfo.InvariantCulture); + var icon = (Icon)Properties.Resources.ResourceManager.GetObject(resourceName, CultureInfo.InvariantCulture)!; if (OSUtils.IsVistaOrLater) return icon.ToMultiResBitmap(); diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs index 8bd023e..92945e0 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Text; using System.Windows.Forms; @@ -35,8 +34,8 @@ internal partial class ColorVisualizerControl : BaseUserControl #region Static Fields - private static Dictionary knownColors; - private static Dictionary systemColors; + private static Dictionary? knownColors; + private static Dictionary? systemColors; #endregion @@ -44,8 +43,8 @@ internal partial class ColorVisualizerControl : BaseUserControl private bool readOnly; private Color color; - private TextureBrush alphaBrush; - private string specialInfo; + private TextureBrush? alphaBrush; + private string? specialInfo; #endregion @@ -141,7 +140,7 @@ internal Color Color } } - internal string SpecialInfo + internal string? SpecialInfo { get => specialInfo; set @@ -185,21 +184,8 @@ public ColorVisualizerControl() #region Static Methods - private static string GetKnownColor(Color color) - { - if (KnownColors.TryGetValue(color.ToArgb(), out string name)) - return name; - - return "-"; - } - - private static string GetSystemColors(Color color) - { - if (SystemColors.TryGetValue(color.ToArgb(), out string name)) - return name; - - return "-"; - } + private static string GetKnownColor(Color color) => KnownColors.GetValueOrDefault(color.ToArgb(), "-"); + private static string GetSystemColors(Color color) => SystemColors.GetValueOrDefault(color.ToArgb(), "-"); #endregion @@ -259,14 +245,13 @@ private void ColorUpdated() private void UpdateInfo() { - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); if (!String.IsNullOrEmpty(SpecialInfo)) sb.AppendLine(SpecialInfo); sb.Append(Res.InfoColor(color.ToArgb(), GetKnownColor(color), GetSystemColors(color), color.GetHue(), color.GetSaturation() * 100f, color.GetBrightness() * 100f)); txtColor.Text = sb.ToString(); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "False alarm, bmpPattern is passed to a brush")] private void CreateAlphaBrush() { Size size = new Size(10, 10).Scale(this.GetScale()); @@ -274,7 +259,7 @@ private void CreateAlphaBrush() using (Graphics g = Graphics.FromImage(bmpPattern)) { g.Clear(Color.White); - Rectangle smallRect = new Rectangle(Point.Empty, new Size(bmpPattern.Width >> 1, bmpPattern.Height >> 1)); + var smallRect = new Rectangle(Point.Empty, new Size(bmpPattern.Width >> 1, bmpPattern.Height >> 1)); g.FillRectangle(Brushes.Silver, smallRect); smallRect.Offset(smallRect.Width, smallRect.Height); g.FillRectangle(Brushes.Silver, smallRect); @@ -299,7 +284,7 @@ private void pnlColor_Paint(object sender, PaintEventArgs e) if (alphaBrush == null) CreateAlphaBrush(); - e.Graphics.FillRectangle(alphaBrush, e.ClipRectangle); + e.Graphics.FillRectangle(alphaBrush!, e.ClipRectangle); } Color backColor = sender == pnlAlpha ? Color.FromArgb(color.A, Color.White) : color; @@ -307,7 +292,7 @@ private void pnlColor_Paint(object sender, PaintEventArgs e) e.Graphics.FillRectangle(b, e.ClipRectangle); } - private void btnEdit_Click(object sender, EventArgs e) + private void btnEdit_Click(object? sender, EventArgs e) { if (readOnly) return; @@ -320,31 +305,31 @@ private void btnEdit_Click(object sender, EventArgs e) } } - private void tbAlpha_Scroll(object sender, EventArgs e) + private void tbAlpha_Scroll(object? sender, EventArgs e) { Color = Color.FromArgb(tbAlpha.Value, color.R, color.G, color.B); OnColorEdited(); } - private void tbRed_Scroll(object sender, EventArgs e) + private void tbRed_Scroll(object? sender, EventArgs e) { Color = Color.FromArgb(color.A, tbRed.Value, color.G, color.B); OnColorEdited(); } - private void tbGreen_Scroll(object sender, EventArgs e) + private void tbGreen_Scroll(object? sender, EventArgs e) { Color = Color.FromArgb(color.A, color.R, tbGreen.Value, color.B); OnColorEdited(); } - private void tbBlue_Scroll(object sender, EventArgs e) + private void tbBlue_Scroll(object? sender, EventArgs e) { Color = Color.FromArgb(color.A, color.R, color.G, tbBlue.Value); OnColorEdited(); } - void ucColorVisualizer_SystemColorsChanged(object sender, EventArgs e) + void ucColorVisualizer_SystemColorsChanged(object? sender, EventArgs e) { systemColors = null; UpdateInfo(); diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererSelectorControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererSelectorControl.cs index aeab33f..80f57fa 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererSelectorControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererSelectorControl.cs @@ -59,14 +59,14 @@ protected override void Dispose(bool disposing) private void InitCommandBindings() { // not for ViewModel.Parameters.PropertyChanged because it is not triggered for expanded properties such as collection elements - CommandBindings.Add(ViewModel.ResetDitherer) + CommandBindings.Add(ViewModel!.ResetDitherer) .AddSource(pgParameters, nameof(pgParameters.PropertyValueChanged)); } private void InitPropertyBindings() { // will not change so not as an actual binding - cmbDitherer.DataSource = ViewModel.Ditherers; + cmbDitherer.DataSource = ViewModel!.Ditherers; // VM.Parameters -> pgParameters.SelectedObject CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Parameters), nameof(pgParameters.SelectedObject), pgParameters); diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs index b3461bb..f617fca 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs @@ -29,7 +29,7 @@ internal partial class DithererStrengthEditorControl : BaseUserControl { #region Fields - private readonly IWindowsFormsEditorService editorService; + private readonly IWindowsFormsEditorService? editorService; private readonly float originalValue; #endregion @@ -104,21 +104,21 @@ protected override bool ProcessDialogKey(Keys keyData) #region Event handlers - private void TrackBar_ValueChanged(object sender, EventArgs e) + private void TrackBar_ValueChanged(object? sender, EventArgs e) { Value = trackBar.Value / 100f; UpdateLabel(); } - private void OKButton_Click(object sender, EventArgs e) + private void OKButton_Click(object? sender, EventArgs e) { - editorService.CloseDropDown(); + editorService?.CloseDropDown(); } - private void CancelButton_Click(object sender, EventArgs e) + private void CancelButton_Click(object? sender, EventArgs e) { Value = originalValue; - editorService.CloseDropDown(); + editorService?.CloseDropDown(); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/MvvmBaseUserControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/MvvmBaseUserControl.cs index cc321da..4cc77c6 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/MvvmBaseUserControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/MvvmBaseUserControl.cs @@ -17,6 +17,7 @@ #region Usings using System; +using System.Diagnostics.CodeAnalysis; using KGySoft.ComponentModel; using KGySoft.Drawing.ImagingTools.ViewModel; @@ -25,12 +26,12 @@ namespace KGySoft.Drawing.ImagingTools.View.UserControls { - internal partial class MvvmBaseUserControl : BaseUserControl + internal class MvvmBaseUserControl : BaseUserControl where TViewModel : IDisposable // BUG: Actually should be ViewModelBase but WinForms designer with derived types dies from that { #region Fields - private TViewModel vm; + private TViewModel? vm; private bool isLoaded; #endregion @@ -39,6 +40,10 @@ internal partial class MvvmBaseUserControl : BaseUserControl #region Internal Properties + /// + /// Gets or sets the view model. Can be null before initializing. Not null if called from . + /// + [MaybeNull] internal TViewModel ViewModel { get => vm; @@ -63,7 +68,7 @@ internal TViewModel ViewModel #region Private Properties // this would not be needed if where TViewModel : ViewModelBase didn't conflict with WinForms designer - private ViewModelBase VM => (ViewModelBase)(object)ViewModel; + private ViewModelBase? VM => (ViewModelBase?)(object?)ViewModel; #endregion @@ -73,7 +78,7 @@ internal TViewModel ViewModel protected MvvmBaseUserControl() { - CommandBindings = new WinformsCommandBindingsCollection(); + CommandBindings = new WinFormsCommandBindingsCollection(); } #endregion @@ -102,20 +107,20 @@ protected virtual void ApplyResources() protected virtual void ApplyViewModel() { - ViewModelBase vm = VM; - vm.ShowInfoCallback = Dialogs.InfoMessage; - vm.ShowWarningCallback = Dialogs.WarningMessage; - vm.ShowErrorCallback = Dialogs.ErrorMessage; - vm.ConfirmCallback = Dialogs.ConfirmMessage; - vm.SynchronizedInvokeCallback = InvokeIfRequired; - - VM.ViewLoaded(); + ViewModelBase vmb = VM!; + vmb.ShowInfoCallback = Dialogs.InfoMessage; + vmb.ShowWarningCallback = Dialogs.WarningMessage; + vmb.ShowErrorCallback = Dialogs.ErrorMessage; + vmb.ConfirmCallback = Dialogs.ConfirmMessage; + vmb.SynchronizedInvokeCallback = InvokeIfRequired; + + vmb.ViewLoaded(); } protected override void Dispose(bool disposing) { if (disposing) - CommandBindings?.Dispose(); + CommandBindings.Dispose(); base.Dispose(disposing); } diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs index 379d31c..c9a6c6b 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs @@ -16,10 +16,9 @@ #region Usings -using System; -using System.ComponentModel; using System.Drawing; using System.Windows.Forms; + using KGySoft.Drawing.ImagingTools.View.Controls; using KGySoft.Drawing.ImagingTools.ViewModel; @@ -31,16 +30,24 @@ internal partial class PreviewImageControl : MvvmBaseUserControl ViewModel.PreviewImage; - set => ViewModel.PreviewImage = value; + get => ViewModel?.PreviewImage; + set + { + if (ViewModel is PreviewImageViewModel vm) + vm.PreviewImage = value; + } } internal bool AutoZoom { - get => ViewModel.AutoZoom; - set => ViewModel.AutoZoom = value; + get => ViewModel?.AutoZoom ?? false; + set + { + if (ViewModel is PreviewImageViewModel vm) + vm.AutoZoom = value; + } } internal ImageViewer ImageViewer => ivPreview; @@ -89,30 +96,30 @@ protected override void ApplyViewModel() private void InitCommandBindings() { - CommandBindings.Add(() => ViewModel.ShowOriginal = true) + CommandBindings.Add(() => ViewModel!.ShowOriginal = true) .AddSource(btnShowOriginal, nameof(btnShowOriginal.MouseDown)); - CommandBindings.Add(() => ViewModel.ShowOriginal = false) + CommandBindings.Add(() => ViewModel!.ShowOriginal = false) .AddSource(btnShowOriginal, nameof(btnShowOriginal.MouseUp)); } private void InitPropertyBindings() { // VM.DisplayImage -> ivPreview.Image - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.DisplayImage), nameof(ivPreview.Image), ivPreview); + CommandBindings.AddPropertyBinding(ViewModel!, nameof(ViewModel.DisplayImage), nameof(ivPreview.Image), ivPreview); // VM.ZoomEnabled -> btnAutoZoom/btnAntiAlias.Enabled - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.ZoomEnabled), nameof(ToolStripItem.Enabled), btnAutoZoom, btnAntiAlias); + CommandBindings.AddPropertyBinding(ViewModel!, nameof(ViewModel.ZoomEnabled), nameof(ToolStripItem.Enabled), btnAutoZoom, btnAntiAlias); // VM.ShowOriginalEnabled -> btnShowOriginal.Enabled - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.ShowOriginalEnabled), nameof(ToolStripItem.Enabled), btnShowOriginal); + CommandBindings.AddPropertyBinding(ViewModel!, nameof(ViewModel.ShowOriginalEnabled), nameof(ToolStripItem.Enabled), btnShowOriginal); // btnAutoZoom.Checked <-> VM.AutoZoom -> ivPreview.AutoZoom - CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.AutoZoom), btnAutoZoom, nameof(btnAutoZoom.Checked)); - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.AutoZoom), nameof(ivPreview.AutoZoom), ivPreview); + CommandBindings.AddTwoWayPropertyBinding(ViewModel!, nameof(ViewModel.AutoZoom), btnAutoZoom, nameof(btnAutoZoom.Checked)); + CommandBindings.AddPropertyBinding(ViewModel!, nameof(ViewModel.AutoZoom), nameof(ivPreview.AutoZoom), ivPreview); // btnAntiAlias.Checked <-> VM.SmoothZooming -> ivPreview.SmoothZooming - CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.SmoothZooming), btnAntiAlias, nameof(btnAntiAlias.Checked)); - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.SmoothZooming), nameof(ivPreview.SmoothZooming), ivPreview); + CommandBindings.AddTwoWayPropertyBinding(ViewModel!, nameof(ViewModel.SmoothZooming), btnAntiAlias, nameof(btnAntiAlias.Checked)); + CommandBindings.AddPropertyBinding(ViewModel!, nameof(ViewModel.SmoothZooming), nameof(ivPreview.SmoothZooming), ivPreview); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerSelectorControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerSelectorControl.cs index 12f333e..64358e2 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerSelectorControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerSelectorControl.cs @@ -59,14 +59,14 @@ protected override void Dispose(bool disposing) private void InitCommandBindings() { // not for ViewModel.Parameters.PropertyChanged because it is not triggered for expanded properties such as collection elements - CommandBindings.Add(ViewModel.ResetQuantizer) + CommandBindings.Add(ViewModel!.ResetQuantizer) .AddSource(pgParameters, nameof(pgParameters.PropertyValueChanged)); } private void InitPropertyBindings() { // will not change so not as an actual binding - cmbQuantizer.DataSource = ViewModel.Quantizers; + cmbQuantizer.DataSource = ViewModel!.Quantizers; // VM.Parameters -> pgParameters.SelectedObject CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Parameters), nameof(pgParameters.SelectedObject), pgParameters); diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs index 29bc0ef..ee2b0f8 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs @@ -29,7 +29,7 @@ internal partial class QuantizerThresholdEditorControl : BaseUserControl { #region Fields - private readonly IWindowsFormsEditorService editorService; + private readonly IWindowsFormsEditorService? editorService; private readonly byte originalValue; #endregion @@ -96,21 +96,21 @@ protected override bool ProcessDialogKey(Keys keyData) #region Event handlers - private void TrackBar_ValueChanged(object sender, EventArgs e) + private void TrackBar_ValueChanged(object? sender, EventArgs e) { Value = (byte)trackBar.Value; lblValue.Text = Value.ToString(CultureInfo.CurrentCulture); } - private void OKButton_Click(object sender, EventArgs e) + private void OKButton_Click(object? sender, EventArgs e) { - editorService.CloseDropDown(); + editorService?.CloseDropDown(); } - private void CancelButton_Click(object sender, EventArgs e) + private void CancelButton_Click(object? sender, EventArgs e) { Value = originalValue; - editorService.CloseDropDown(); + editorService?.CloseDropDown(); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/WinformsCommandBindingsCollection.cs b/KGySoft.Drawing.ImagingTools/View/WinformsCommandBindingsCollection.cs index 7dba35c..25d69db 100644 --- a/KGySoft.Drawing.ImagingTools/View/WinformsCommandBindingsCollection.cs +++ b/KGySoft.Drawing.ImagingTools/View/WinformsCommandBindingsCollection.cs @@ -1,7 +1,7 @@ #region Copyright /////////////////////////////////////////////////////////////////////////////// -// File: WinformsCommandBindingsCollection.cs +// File: WinFormsCommandBindingsCollection.cs /////////////////////////////////////////////////////////////////////////////// // Copyright (C) KGy SOFT, 2005-2020 - All Rights Reserved // @@ -30,11 +30,11 @@ namespace KGySoft.Drawing.ImagingTools.View /// By using this collection the properties (eg. but also any other added property) /// of the added bindings will be synced with the command sources. /// - internal class WinformsCommandBindingsCollection : CommandBindingsCollection + internal class WinFormsCommandBindingsCollection : CommandBindingsCollection { #region Methods - public override ICommandBinding Add(ICommand command, IDictionary initialState = null, bool disposeCommand = false) + public override ICommandBinding Add(ICommand command, IDictionary? initialState = null, bool disposeCommand = false) => base.Add(command, initialState, disposeCommand) .AddStateUpdater(PropertyCommandStateUpdater.Updater); diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/EventHandlerExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/EventHandlerExtensions.cs index 6e08a6e..6bdc438 100644 --- a/KGySoft.Drawing.ImagingTools/View/_Extensions/EventHandlerExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/EventHandlerExtensions.cs @@ -27,7 +27,7 @@ internal static class EventHandlerExtensions { #region Methods - internal static TDelegate GetHandler(this EventHandlerList handlers, object key) where TDelegate : Delegate => handlers?[key] as TDelegate; + internal static TDelegate? GetHandler(this EventHandlerList? handlers, object key) where TDelegate : Delegate => handlers?[key] as TDelegate; #endregion } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/AdjustBrightnessViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/AdjustBrightnessViewModel.cs index 9f04228..0959f06 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/AdjustBrightnessViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/AdjustBrightnessViewModel.cs @@ -43,9 +43,9 @@ internal GenerateTask(float value, ColorChannels colorChannels) #region Methods internal override IAsyncResult BeginGenerate(AsyncConfig asyncConfig) - => BitmapData.BeginAdjustBrightness(Value, channels: ColorChannels, asyncConfig: asyncConfig); + => BitmapData!.BeginAdjustBrightness(Value, channels: ColorChannels, asyncConfig: asyncConfig); - internal override Bitmap EndGenerate(IAsyncResult asyncResult) + internal override Bitmap? EndGenerate(IAsyncResult asyncResult) { asyncResult.EndAdjustBrightness(); return base.EndGenerate(asyncResult); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/AdjustColorsViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/AdjustColorsViewModelBase.cs index 5bdbc50..0fc7817 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/AdjustColorsViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/AdjustColorsViewModelBase.cs @@ -17,8 +17,8 @@ #region Usings using System; -using System.Diagnostics.CodeAnalysis; using System.Drawing; + using KGySoft.ComponentModel; using KGySoft.CoreLibraries; using KGySoft.Drawing.Imaging; @@ -35,14 +35,14 @@ protected abstract class AdjustColorsTaskBase : GenerateTaskBase { #region Fields - private Bitmap result; + private Bitmap? result; #endregion #region Properties #region Internal Properties - + internal float Value { get; } internal ColorChannels ColorChannels { get; } @@ -50,7 +50,7 @@ protected abstract class AdjustColorsTaskBase : GenerateTaskBase #region Protected Properties - protected IReadWriteBitmapData BitmapData { get; private set; } + protected IReadWriteBitmapData? BitmapData { get; private set; } #endregion @@ -70,8 +70,6 @@ protected AdjustColorsTaskBase(float value, ColorChannels colorChannels) #region Internal Methods - [SuppressMessage("Reliability", "CA2002:Do not lock on objects with weak identity", - Justification = "False alarm, source is never a remote object")] internal override void Initialize(Bitmap source, bool isInUse) { lock (source) @@ -79,11 +77,11 @@ internal override void Initialize(Bitmap source, bool isInUse) BitmapData = result.GetReadWriteBitmapData(); } - internal override Bitmap EndGenerate(IAsyncResult asyncResult) + internal override Bitmap? EndGenerate(IAsyncResult asyncResult) { // If there was no exception returning result and clearing the field to prevent disposing. // The caller will take care of disposing if the operation was canceled and the result is discarded. - Bitmap bmp = result; + Bitmap? bmp = result; result = null; return bmp; } @@ -94,7 +92,7 @@ internal override void SetCompleted() BitmapData = null; base.SetCompleted(); } - + #endregion #region Protected Methods @@ -122,9 +120,9 @@ protected override void Dispose(bool disposing) #region Properties #region Internal Properties - + internal ColorChannels ColorChannels { get => Get(ColorChannels.Rgb); set => Set(value); } - internal float Value { get => Get(DefaultValue); set => Set(value); } + internal float Value { get => Get(DefaultValue); set => Set(value); } internal virtual float MinValue => -1f; internal virtual float MaxValue => 1f; diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/AdjustContrastViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/AdjustContrastViewModel.cs index 27bf3fb..6424d51 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/AdjustContrastViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/AdjustContrastViewModel.cs @@ -43,9 +43,9 @@ internal GenerateTask(float value, ColorChannels colorChannels) #region Methods internal override IAsyncResult BeginGenerate(AsyncConfig asyncConfig) - => BitmapData.BeginAdjustContrast(Value, channels: ColorChannels, asyncConfig: asyncConfig); + => BitmapData!.BeginAdjustContrast(Value, channels: ColorChannels, asyncConfig: asyncConfig); - internal override Bitmap EndGenerate(IAsyncResult asyncResult) + internal override Bitmap? EndGenerate(IAsyncResult asyncResult) { asyncResult.EndAdjustContrast(); return base.EndGenerate(asyncResult); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/AdjustGammaViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/AdjustGammaViewModel.cs index d89e9a2..ec20891 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/AdjustGammaViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/AdjustGammaViewModel.cs @@ -43,9 +43,9 @@ internal GenerateTask(float value, ColorChannels colorChannels) #region Methods internal override IAsyncResult BeginGenerate(AsyncConfig asyncConfig) - => BitmapData.BeginAdjustGamma(Value, channels: ColorChannels, asyncConfig: asyncConfig); + => BitmapData!.BeginAdjustGamma(Value, channels: ColorChannels, asyncConfig: asyncConfig); - internal override Bitmap EndGenerate(IAsyncResult asyncResult) + internal override Bitmap? EndGenerate(IAsyncResult asyncResult) { asyncResult.EndAdjustGamma(); return base.EndGenerate(asyncResult); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/BitmapDataVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/BitmapDataVisualizerViewModel.cs index 2a096b5..e890d11 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/BitmapDataVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/BitmapDataVisualizerViewModel.cs @@ -32,7 +32,7 @@ internal class BitmapDataVisualizerViewModel : ImageVisualizerViewModel #region Internal Properties - internal BitmapDataInfo BitmapDataInfo { get => Get(); set => Set(value); } + internal BitmapDataInfo? BitmapDataInfo { get => Get(); init => Set(value); } #endregion @@ -60,7 +60,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) base.OnPropertyChanged(e); if (e.PropertyName == nameof(BitmapDataInfo)) { - var bitmapDataInfo = (BitmapDataInfo)e.NewValue; + var bitmapDataInfo = (BitmapDataInfo?)e.NewValue; Image = bitmapDataInfo?.BackingImage; if ((bitmapDataInfo?.BitmapData?.PixelFormat ?? PixelFormat.Format32bppArgb).ToBitsPerPixel() <= 8) Notification = Res.NotificationPaletteCannotBeRestored; @@ -69,8 +69,8 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) protected override void UpdateInfo() { - BitmapDataInfo bitmapDataInfo = BitmapDataInfo; - BitmapData bitmapData = bitmapDataInfo?.BitmapData; + BitmapDataInfo? bitmapDataInfo = BitmapDataInfo; + BitmapData? bitmapData = bitmapDataInfo?.BitmapData; if (bitmapDataInfo?.BackingImage == null || bitmapData == null) { diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ColorSpaceViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ColorSpaceViewModel.cs index 6dc8241..2f8ebf3 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ColorSpaceViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ColorSpaceViewModel.cs @@ -18,7 +18,6 @@ using System; using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Imaging; using System.Linq; @@ -40,7 +39,7 @@ private sealed class GenerateTask : GenerateTaskBase { #region Fields - private Bitmap sourceBitmap; + private Bitmap? sourceBitmap; private bool isSourceCloned; #endregion @@ -48,14 +47,14 @@ private sealed class GenerateTask : GenerateTaskBase #region Properties internal PixelFormat PixelFormat { get; } - internal IQuantizer Quantizer { get; } - internal IDitherer Ditherer { get; } + internal IQuantizer? Quantizer { get; } + internal IDitherer? Ditherer { get; } #endregion #region Constructors - internal GenerateTask(PixelFormat pixelFormat, IQuantizer quantizer, IDitherer ditherer) + internal GenerateTask(PixelFormat pixelFormat, IQuantizer? quantizer, IDitherer? ditherer) { PixelFormat = pixelFormat; Quantizer = quantizer; @@ -66,8 +65,6 @@ internal GenerateTask(PixelFormat pixelFormat, IQuantizer quantizer, IDitherer d #region Methods - [SuppressMessage("Reliability", "CA2002:Do not lock on objects with weak identity", - Justification = "False alarm, source is never a remote object")] internal override void Initialize(Bitmap source, bool isInUse) { // Locking on source image to avoid "bitmap region is already locked" if the UI is painting the image when we clone it. @@ -90,9 +87,9 @@ internal override void Initialize(Bitmap source, bool isInUse) } internal override IAsyncResult BeginGenerate(AsyncConfig asyncConfig) - => sourceBitmap.BeginConvertPixelFormat(PixelFormat, Quantizer, Ditherer, asyncConfig); + => sourceBitmap!.BeginConvertPixelFormat(PixelFormat, Quantizer, Ditherer, asyncConfig); - internal override Bitmap EndGenerate(IAsyncResult asyncResult) => asyncResult.EndConvertPixelFormat(); + internal override Bitmap? EndGenerate(IAsyncResult asyncResult) => asyncResult.EndConvertPixelFormat(); internal override void SetCompleted() { @@ -102,7 +99,7 @@ internal override void SetCompleted() sourceBitmap = null; } else - Monitor.Exit(sourceBitmap); + Monitor.Exit(sourceBitmap!); base.SetCompleted(); } @@ -187,10 +184,10 @@ protected override ValidationResultsCollection DoValidation() bool useQuantizer = UseQuantizer; bool useDitherer = UseDitherer; PixelFormat pixelFormat = changePixelFormat ? PixelFormat : originalPixelFormat; - IQuantizer quantizer = useQuantizer ? QuantizerSelectorViewModel.Quantizer : null; - IDitherer ditherer = useDitherer ? DithererSelectorViewModel.Ditherer : null; - Exception quantizerError = useQuantizer ? QuantizerSelectorViewModel.CreateQuantizerError : null; - Exception dithererError = useDitherer ? DithererSelectorViewModel.CreateDithererError : null; + IQuantizer? quantizer = useQuantizer ? QuantizerSelectorViewModel.Quantizer : null; + IDitherer? ditherer = useDitherer ? DithererSelectorViewModel.Ditherer : null; + Exception? quantizerError = useQuantizer ? QuantizerSelectorViewModel.CreateQuantizerError : null; + Exception? dithererError = useDitherer ? DithererSelectorViewModel.CreateDithererError : null; int bpp = pixelFormat.ToBitsPerPixel(); int originalBpp = originalPixelFormat.ToBitsPerPixel(); int? bppHint = quantizer?.PixelFormatHint.ToBitsPerPixel(); @@ -202,7 +199,7 @@ protected override ValidationResultsCollection DoValidation() if (quantizerError != null) result.AddError(nameof(QuantizerSelectorViewModel.Quantizer), Res.ErrorMessageFailedToInitializeQuantizer(quantizerError.Message)); else if (bppHint <= 8 && bppHint > bpp) - result.AddError(nameof(QuantizerSelectorViewModel.Quantizer), Res.ErrorMessageQuantizerPaletteTooLarge(pixelFormat, quantizer.PixelFormatHint, 1 << bpp)); + result.AddError(nameof(QuantizerSelectorViewModel.Quantizer), Res.ErrorMessageQuantizerPaletteTooLarge(pixelFormat, quantizer!.PixelFormatHint, 1 << bpp)); if (dithererError != null) result.AddError(nameof(DithererSelectorViewModel.Ditherer), Res.ErrorMessageFailedToInitializeDitherer(dithererError.Message)); @@ -215,7 +212,7 @@ protected override ValidationResultsCollection DoValidation() result.AddWarning(nameof(PixelFormat), Res.WarningMessageWideConversionLoss(originalPixelFormat)); if (bppHint > bpp) - result.AddWarning(nameof(QuantizerSelectorViewModel.Quantizer), Res.WarningMessageQuantizerTooWide(pixelFormat, quantizer.PixelFormatHint)); + result.AddWarning(nameof(QuantizerSelectorViewModel.Quantizer), Res.WarningMessageQuantizerTooWide(pixelFormat, quantizer!.PixelFormatHint)); if (bppHint == 32 && ditherer != null) result.AddWarning(nameof(DithererSelectorViewModel.Ditherer), Res.WarningMessageDithererNoAlphaGradient); @@ -224,7 +221,7 @@ protected override ValidationResultsCollection DoValidation() if (changePixelFormat && pixelFormat == originalPixelFormat) result.AddInfo(nameof(PixelFormat), Res.InfoMessageSamePixelFormat); if (bppHint < bpp) - result.AddInfo(nameof(PixelFormat), Res.InfoMessagePixelFormatUnnecessarilyWide(quantizer.PixelFormatHint)); + result.AddInfo(nameof(PixelFormat), Res.InfoMessagePixelFormatUnnecessarilyWide(quantizer!.PixelFormatHint)); if (!useQuantizer) { @@ -240,9 +237,9 @@ protected override ValidationResultsCollection DoValidation() else if (bppHint == 32 && originalBpp >= 32 && !originalPixelFormat.HasAlpha()) result.AddInfo(nameof(QuantizerSelectorViewModel.Quantizer), Res.InfoMessageArgbQuantizerHasNoEffect); - if (bppHint < originalBpp && !useDitherer && quantizer.PixelFormatHint.CanBeDithered()) + if (bppHint < originalBpp && !useDitherer && quantizer!.PixelFormatHint.CanBeDithered()) { - if (QuantizerSelectorViewModel.SelectedQuantizer.Method.Name != nameof(PredefinedColorsQuantizer.Grayscale)) + if (QuantizerSelectorViewModel.SelectedQuantizer!.Method.Name != nameof(PredefinedColorsQuantizer.Grayscale)) result.AddInfo(nameof(DithererSelectorViewModel.Ditherer), Res.InfoMessageQuantizerCanBeDithered(originalPixelFormat)); } else if (bpp < originalBpp && !useDitherer && pixelFormat.CanBeDithered()) @@ -282,8 +279,8 @@ protected override void Dispose(bool disposing) if (disposing) { // These disposals remove every subscriptions as well - QuantizerSelectorViewModel?.Dispose(); - DithererSelectorViewModel?.Dispose(); + QuantizerSelectorViewModel.Dispose(); + DithererSelectorViewModel.Dispose(); } base.Dispose(disposing); @@ -297,7 +294,7 @@ protected override void Dispose(bool disposing) #region Event Handlers - private void Selector_PropertyChanged(object sender, PropertyChangedEventArgs e) + private void Selector_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName.In(nameof(QuantizerSelectorViewModel.Quantizer), nameof(QuantizerSelectorViewModel.CreateQuantizerError), nameof(DithererSelectorViewModel.Ditherer), nameof(DithererSelectorViewModel.CreateDithererError))) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs index 66f8ea4..915de12 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs @@ -17,7 +17,6 @@ #region Usings using System; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Threading; @@ -39,7 +38,7 @@ private sealed class CountTask : AsyncTaskBase { #region Fields - internal Bitmap Bitmap; + internal Bitmap Bitmap = default!; #endregion } @@ -52,7 +51,7 @@ private sealed class CountTask : AsyncTaskBase private readonly DrawingProgressManager drawingProgressManager; - private volatile CountTask task; + private volatile CountTask? task; private int? colorCount; #endregion @@ -90,7 +89,7 @@ internal CountColorsViewModel(Bitmap bitmap) public int? GetEditedModel() { - task.WaitForCompletion(); + task?.WaitForCompletion(); return colorCount; } @@ -100,9 +99,13 @@ internal CountColorsViewModel(Bitmap bitmap) internal void CancelIfRunning() { - task.IsCanceled = true; + CountTask? t = task; + if (t == null) + return; + + t.IsCanceled = true; SetModified(false); - task.WaitForCompletion(); + t.WaitForCompletion(); } #endregion @@ -122,16 +125,15 @@ private void BeginCountColors(Bitmap bitmap) ThreadPool.QueueUserWorkItem(DoCountColors); } - [SuppressMessage("Reliability", "CA2002:Do not lock on objects with weak identity", Justification = "False alarm, task.Bitmap is not a remote object")] - private void DoCountColors(object state) + private void DoCountColors(object? state) { - Exception error = null; + Exception? error = null; // We must lock on the image to avoid the possible "bitmap region is already in use" error from the Paint of main view's image viewer, // which also locks on the image to help avoiding this error - lock (task.Bitmap) + lock (task!.Bitmap) { - IReadableBitmapData bitmapData = null; + IReadableBitmapData? bitmapData = null; try { bitmapData = task.Bitmap.GetReadableBitmapData(); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DefaultViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DefaultViewModel.cs index 5009112..a676f40 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DefaultViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DefaultViewModel.cs @@ -16,8 +16,8 @@ #region Usings -using System; using System.IO; + using KGySoft.CoreLibraries; using KGySoft.Drawing.ImagingTools.Model; @@ -31,8 +31,8 @@ internal class DefaultViewModel : ImageVisualizerViewModel #region Internal Properties - internal string[] CommandLineArguments { get => Get(); set => Set(value); } - internal string FileName { get => Get(); set => Set(value); } + internal string[]? CommandLineArguments { get => Get(); init => Set(value); } + internal string? FileName { get => Get(); set => Set(value); } #endregion @@ -50,11 +50,11 @@ internal class DefaultViewModel : ImageVisualizerViewModel internal override void ViewLoaded() { - string[] args = CommandLineArguments; - if (!args.IsNullOrEmpty()) - ProcessArgs(CommandLineArguments); - else + string[]? args = CommandLineArguments; + if (args.IsNullOrEmpty()) UpdateInfo(); + else + ProcessArgs(args!); base.ViewLoaded(); } @@ -127,7 +127,7 @@ protected override void Clear() private void ProcessArgs(string[] args) { - if (args.IsNullOrEmpty()) + if (args.Length == 0) return; string file = args[0]; if (!File.Exists(file)) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DithererSelectorViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DithererSelectorViewModel.cs index e8642fa..7d91efe 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DithererSelectorViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DithererSelectorViewModel.cs @@ -18,10 +18,9 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; -using System.Drawing; using System.Reflection; + using KGySoft.ComponentModel; using KGySoft.Drawing.Imaging; using KGySoft.Drawing.ImagingTools.Model; @@ -37,10 +36,10 @@ internal class DithererSelectorViewModel : ViewModelBase // not a static property so always can be reinitialized with the current language internal IList Ditherers => Get(InitDitherers); - internal DithererDescriptor SelectedDitherer { get => Get(); private set => Set(value); } - internal CustomPropertiesObject Parameters { get => Get(); private set => Set(value); } - internal IDitherer Ditherer { get => Get(); private set => Set(value); } - internal Exception CreateDithererError { get => Get(); set => Set(value); } + internal DithererDescriptor? SelectedDitherer { get => Get(); private set => Set(value); } + internal CustomPropertiesObject? Parameters { get => Get(); private set => Set(value); } + internal IDitherer? Ditherer { get => Get(); private set => Set(value); } + internal Exception? CreateDithererError { get => Get(); set => Set(value); } #endregion @@ -70,8 +69,8 @@ private static IList InitDitherers() => new DithererDescriptor(typeof(ErrorDiffusionDitherer), nameof(ErrorDiffusionDitherer.StevensonArce)), new DithererDescriptor(typeof(ErrorDiffusionDitherer), nameof(ErrorDiffusionDitherer.Stucki)), - new DithererDescriptor(typeof(RandomNoiseDitherer).GetConstructor(new[] { typeof(float), typeof(int?) })), - new DithererDescriptor(typeof(InterleavedGradientNoiseDitherer).GetConstructor(new[] { typeof(float) })), + new DithererDescriptor(typeof(RandomNoiseDitherer).GetConstructor(new[] { typeof(float), typeof(int?) })!), + new DithererDescriptor(typeof(InterleavedGradientNoiseDitherer).GetConstructor(new[] { typeof(float) })!), }; #endregion @@ -82,16 +81,17 @@ private static IList InitDitherers() => internal void ResetDitherer() { - DithererDescriptor descriptor = SelectedDitherer; + DithererDescriptor? descriptor = SelectedDitherer; + CustomPropertiesObject? parameters = Parameters; CreateDithererError = null; - if (descriptor == null) + + if (descriptor == null || parameters == null) { Ditherer = null; return; } - IDitherer ditherer = null; - CustomPropertiesObject parameterValues = Parameters; + IDitherer? ditherer = null; try { foreach (MemberInfo memberInfo in descriptor.InvokeChain) @@ -100,17 +100,17 @@ internal void ResetDitherer() { case ConstructorInfo ctor: Debug.Assert(ditherer == null); - ditherer = (IDitherer)CreateInstanceAccessor.GetAccessor(ctor).CreateInstance(descriptor.EvaluateParameters(ctor.GetParameters(), parameterValues)); + ditherer = (IDitherer)CreateInstanceAccessor.GetAccessor(ctor).CreateInstance(descriptor.EvaluateParameters(ctor.GetParameters(), parameters)); break; case PropertyInfo property: - Debug.Assert(ditherer == null && property.GetGetMethod().IsStatic); - ditherer = (IDitherer)PropertyAccessor.GetAccessor(property).Get(null); + Debug.Assert(ditherer == null && property.GetGetMethod()!.IsStatic); + ditherer = (IDitherer)PropertyAccessor.GetAccessor(property).Get(null)!; break; case MethodInfo method: Debug.Assert(ditherer != null && !method.IsStatic); - ditherer = (IDitherer)MethodAccessor.GetAccessor(method).Invoke(ditherer, descriptor.EvaluateParameters(method.GetParameters(), parameterValues)); + ditherer = (IDitherer)MethodAccessor.GetAccessor(method).Invoke(ditherer, descriptor.EvaluateParameters(method.GetParameters(), parameters))!; break; default: @@ -134,19 +134,18 @@ internal void ResetDitherer() protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) { base.OnPropertyChanged(e); - if (e.PropertyName == nameof(SelectedDitherer)) + switch (e.PropertyName) { - CustomPropertiesObject previousParameters = Parameters; - Parameters = previousParameters == null - ? new CustomPropertiesObject(SelectedDitherer.Parameters) - : new CustomPropertiesObject(previousParameters, SelectedDitherer.Parameters); - return; - } - - if (e.PropertyName == nameof(Parameters)) - { - ResetDitherer(); - return; + case nameof(SelectedDitherer): + CustomPropertiesObject? previousParameters = Parameters; + Parameters = previousParameters == null + ? new CustomPropertiesObject(SelectedDitherer!.Parameters) + : new CustomPropertiesObject(previousParameters, SelectedDitherer!.Parameters); + return; + + case nameof(Parameters): + ResetDitherer(); + return; } } @@ -154,7 +153,6 @@ protected override void Dispose(bool disposing) { if (IsDisposed) return; - CustomPropertiesObject parameters = Parameters; base.Dispose(disposing); } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/GraphicsVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/GraphicsVisualizerViewModel.cs index c0dee60..6035a5c 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/GraphicsVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/GraphicsVisualizerViewModel.cs @@ -39,10 +39,10 @@ internal class GraphicsVisualizerViewModel : ImageVisualizerViewModel #region Properties - internal GraphicsInfo GraphicsInfo { get => Get(); set => Set(value); } + internal GraphicsInfo? GraphicsInfo { get => Get(); init => Set(value); } internal bool Crop { get => Get(); set => Set(value); } - internal bool HighlightVisibleClip { get => Get(true); set => Set(value); } - internal Action DrawFocusRectangleCallback { get => Get>(); set => Set(value); } + internal bool HighlightVisibleClip { get => Get(true); set => Set(value); } + internal Action? DrawFocusRectangleCallback { get => Get?>(); set => Set(value); } internal ICommandState CropCommandState => Get(() => new CommandState()); internal ICommandState HighlightVisibleClipCommandState => Get(() => new CommandState()); @@ -81,8 +81,8 @@ internal override void ViewLoaded() protected override void UpdateInfo() { - GraphicsInfo graphicsInfo = GraphicsInfo; - Matrix transform = graphicsInfo?.Transform; + GraphicsInfo? graphicsInfo = GraphicsInfo; + Matrix? transform = graphicsInfo?.Transform; if (graphicsInfo?.GraphicsImage == null || transform == null) { TitleCaption = Res.TitleNoImage; @@ -142,9 +142,9 @@ protected override void Dispose(bool disposing) private void UpdateImageAndCommands() { UpdateGraphicImage(); - GraphicsInfo graphicsInfo = GraphicsInfo; - Bitmap backingImage = graphicsInfo?.GraphicsImage; - bool commandsEnabled = backingImage != null && (backingImage.Size != graphicsInfo.OriginalVisibleClipBounds.Size || graphicsInfo.OriginalVisibleClipBounds.Location != Point.Empty); + GraphicsInfo? graphicsInfo = GraphicsInfo; + Bitmap? backingImage = graphicsInfo?.GraphicsImage; + bool commandsEnabled = backingImage != null && (backingImage.Size != graphicsInfo!.OriginalVisibleClipBounds.Size || graphicsInfo.OriginalVisibleClipBounds.Location != Point.Empty); CropCommandState.Enabled = HighlightVisibleClipCommandState.Enabled = commandsEnabled; } @@ -152,18 +152,18 @@ private void UpdateGraphicImage() { if (!viewInitialized) return; - GraphicsInfo graphicsInfo = GraphicsInfo; - Bitmap backingImage = graphicsInfo?.GraphicsImage; + GraphicsInfo? graphicsInfo = GraphicsInfo; + Bitmap? backingImage = graphicsInfo?.GraphicsImage; if (backingImage == null) return; - Rectangle visibleRect = graphicsInfo.OriginalVisibleClipBounds; + Rectangle visibleRect = graphicsInfo!.OriginalVisibleClipBounds; if (Crop && (visibleRect.Size != backingImage.Size || visibleRect.Location != Point.Empty)) { if (visibleRect.Width <= 0 || visibleRect.Height <= 0) return; - Bitmap newImage = new Bitmap(visibleRect.Width, visibleRect.Height); + var newImage = new Bitmap(visibleRect.Width, visibleRect.Height); using (Graphics g = Graphics.FromImage(newImage)) g.DrawImage(backingImage, new Rectangle(Point.Empty, visibleRect.Size), visibleRect, GraphicsUnit.Pixel); @@ -173,7 +173,7 @@ private void UpdateGraphicImage() if (HighlightVisibleClip && (visibleRect.Size != backingImage.Size || visibleRect.Location != Point.Empty)) { - Bitmap newImage = new Bitmap(backingImage); + var newImage = new Bitmap(backingImage); using (Graphics g = Graphics.FromImage(newImage)) { using (Brush b = new SolidBrush(Color.FromArgb(128, Color.Black))) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs index 0750d09..8dcaf34 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs @@ -34,7 +34,7 @@ namespace KGySoft.Drawing.ImagingTools.ViewModel { - internal class ImageVisualizerViewModel : ViewModelBase, IViewModel, IViewModel, IViewModel, IViewModel, IViewModel + internal class ImageVisualizerViewModel : ViewModelBase, IViewModel, IViewModel, IViewModel, IViewModel, IViewModel { #region Constants @@ -72,47 +72,48 @@ internal class ImageVisualizerViewModel : ViewModelBase, IViewModel, #region Internal Properties - internal Image Image + internal Image? Image { get => imageInfo.GetCreateImage(); set => SetImageInfo(new ImageInfo(value)); } - internal Icon Icon + internal Icon? Icon { get => imageInfo.Icon; set => SetImageInfo(new ImageInfo(value)); } + [AllowNull] internal ImageInfo ImageInfo { get => imageInfo; set => SetImageInfo(value ?? new ImageInfo(ImageInfoType.None)); } - internal Image PreviewImage { get => Get(); set => Set(value); } + internal Image? PreviewImage { get => Get(); set => Set(value); } internal bool ReadOnly { get => Get(); set => Set(value); } - internal string TitleCaption { get => Get(); set => Set(value); } - internal string InfoText { get => Get(); set => Set(value); } - internal string Notification { get => Get(); set => Set(value); } + internal string? TitleCaption { get => Get(); set => Set(value); } + internal string? InfoText { get => Get(); set => Set(value); } + internal string? Notification { get => Get(); set => Set(value); } internal bool AutoZoom { get => Get(); set => Set(value); } internal float Zoom { get => Get(1f); set => Set(value); } internal bool SmoothZooming { get => Get(); set => Set(value); } internal bool IsCompoundView { get => Get(true); set => Set(value); } internal bool IsAutoPlaying { get => Get(); set => Set(value); } - internal string OpenFileFilter { get => Get(); set => Set(value); } - internal string SaveFileFilter { get => Get(); set => Set(value); } + internal string? OpenFileFilter { get => Get(); set => Set(value); } + internal string? SaveFileFilter { get => Get(); set => Set(value); } internal int SaveFileFilterIndex { get => Get(); set => Set(value); } - internal string SaveFileDefaultExtension { get => Get(); set => Set(value); } + internal string? SaveFileDefaultExtension { get => Get(); set => Set(value); } - internal Func GetScreenRectangleCallback { get => Get>(); set => Set(value); } - internal Func GetViewSizeCallback { get => Get>(); set => Set(value); } - internal Func GetImagePreviewSizeCallback { get => Get>(); set => Set(value); } - internal Action ApplyViewSizeCallback { get => Get>(); set => Set(value); } - internal Func SelectFileToOpenCallback { get => Get>(); set => Set(value); } - internal Func SelectFileToSaveCallback { get => Get>(); set => Set(value); } - internal Action UpdatePreviewImageCallback { get => Get(); set => Set(value); } - internal Func GetCompoundViewIconCallback { get => Get>(); set => Set(value); } + internal Func? GetScreenRectangleCallback { get => Get?>(); set => Set(value); } + internal Func? GetViewSizeCallback { get => Get?>(); set => Set(value); } + internal Func? GetImagePreviewSizeCallback { get => Get?>(); set => Set(value); } + internal Action? ApplyViewSizeCallback { get => Get?>(); set => Set(value); } + internal Func? SelectFileToOpenCallback { get => Get?>(); set => Set(value); } + internal Func? SelectFileToSaveCallback { get => Get?>(); set => Set(value); } + internal Action? UpdatePreviewImageCallback { get => Get(); set => Set(value); } + internal Func? GetCompoundViewIconCallback { get => Get?>(); set => Set(value); } internal ICommandState SetAutoZoomCommandState => Get(() => new CommandState()); internal ICommandState SetSmoothZoomingCommandState => Get(() => new CommandState()); @@ -201,9 +202,7 @@ private static string RawFormatToString(Guid imageFormat) return Res.InfoUnknownFormat(imageFormat); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "Not disposing reader because stream must be left open and the constructor with leaveOpen parameter is not available for every platform")] - private static bool TryLoadCustom(MemoryStream stream, out Image image) + private static bool TryLoadCustom(MemoryStream stream, [MaybeNullWhen(false)]out Image image) { const int bdatHeader = 0x54414442; // "BDAT" image = null; @@ -269,7 +268,7 @@ protected virtual void UpdateInfo() } ImageInfoBase currentImage = GetCurrentImage(); - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); sb.Append(Res.TitleType(GetTypeName())); if (!imageInfo.IsMetafile) { @@ -299,7 +298,7 @@ protected virtual void UpdateInfo() protected virtual void OpenFile() { SetOpenFilter(); - string fileName = SelectFileToOpenCallback?.Invoke(); + string? fileName = SelectFileToOpenCallback?.Invoke(); if (fileName == null) return; @@ -324,45 +323,45 @@ protected virtual bool OpenFile(string path) protected virtual bool SaveFile(string fileName, string selectedFormat) { - ImageCodecInfo encoder = encoderCodecs.FirstOrDefault(e => e.FilenameExtension.Equals(selectedFormat, StringComparison.OrdinalIgnoreCase)); + ImageCodecInfo? encoder = encoderCodecs.FirstOrDefault(e => selectedFormat.Equals(e.FilenameExtension, StringComparison.OrdinalIgnoreCase)); try { // BMP if (encoder?.FormatID == ImageFormat.Bmp.Guid) - GetCurrentImage().Image.SaveAsBmp(fileName); + GetCurrentImage().Image!.SaveAsBmp(fileName); // JPEG else if (encoder?.FormatID == ImageFormat.Jpeg.Guid) - GetCurrentImage().Image.SaveAsJpeg(fileName, 95); + GetCurrentImage().Image!.SaveAsJpeg(fileName, 95); // GIF else if (encoder?.FormatID == ImageFormat.Gif.Guid) - GetCurrentImage().Image.SaveAsGif(fileName); + GetCurrentImage().Image!.SaveAsGif(fileName); // Tiff else if (encoder?.FormatID == ImageFormat.Tiff.Guid) { if (imageInfo.HasFrames && IsCompoundView) { using (Stream stream = File.Create(fileName)) - imageInfo.Frames.Select(f => f.Image).SaveAsMultipageTiff(stream); + imageInfo.Frames!.Select(f => f.Image!).SaveAsMultipageTiff(stream); } else - GetCurrentImage().Image.SaveAsTiff(fileName); + GetCurrentImage().Image!.SaveAsTiff(fileName); } // PNG else if (encoder?.FormatID == ImageFormat.Png.Guid) - GetCurrentImage().Image.SaveAsPng(fileName); + GetCurrentImage().Image!.SaveAsPng(fileName); // icon else if (selectedFormat == "*.ico") SaveIcon(fileName); // windows metafile else if (selectedFormat == "*.wmf" && imageInfo.IsMetafile) - ((Metafile)imageInfo.Image).SaveAsWmf(fileName); + ((Metafile)imageInfo.Image!).SaveAsWmf(fileName); // enhanced metafile else if (selectedFormat == "*.emf" && imageInfo.IsMetafile) - ((Metafile)imageInfo.Image).SaveAsEmf(fileName); + ((Metafile)imageInfo.Image!).SaveAsEmf(fileName); // Some unrecognized encoder - we assume it can handle every pixel format else if (encoder != null) - GetCurrentImage().Image.Save(fileName, encoder, null); + GetCurrentImage().Image!.Save(fileName, encoder, null); else if (selectedFormat == "*.bdat") SaveBitmapData(fileName); else @@ -396,7 +395,6 @@ protected override void Dispose(bool disposing) private void SetImageInfo(ImageInfo value) { - Debug.Assert(value != null); ValidateImageInfo(value); currentResolution = Size.Empty; @@ -459,7 +457,7 @@ private ImageInfoBase GetCurrentImage() { if (!imageInfo.HasFrames || currentFrame < 0 || IsAutoPlaying) return imageInfo; - return imageInfo.Frames[currentFrame]; + return imageInfo.Frames![currentFrame]; } private bool IsSingleImageShown() => imageInfo.Type != ImageInfoType.None && !imageInfo.HasFrames @@ -467,10 +465,10 @@ private bool IsSingleImageShown() => imageInfo.Type != ImageInfoType.None && !im private void SetCompoundViewCommandStateImage() { - Func callback = GetCompoundViewIconCallback; + Func? callback = GetCompoundViewIconCallback; deferSettingCompoundStateImage = callback == null; if (callback != null) - SetCompoundViewCommandState[stateImage] = callback?.Invoke(imageInfo.Type); + SetCompoundViewCommandState[stateImage] = callback.Invoke(imageInfo.Type); } private void ImageChanged() @@ -499,14 +497,14 @@ private string GetFrameInfo(bool singleLine) if (!imageInfo.HasFrames) return String.Empty; - StringBuilder result = new StringBuilder(); + var result = new StringBuilder(); if (singleLine) result.Append("; "); if (currentFrame != -1 && !IsAutoPlaying) - result.Append(Res.InfoCurrentFrame(currentFrame + 1, imageInfo.Frames.Length)); + result.Append(Res.InfoCurrentFrame(currentFrame + 1, imageInfo.Frames!.Length)); else - result.Append(Res.InfoFramesCount(imageInfo.Frames.Length)); + result.Append(Res.InfoFramesCount(imageInfo.Frames!.Length)); if (!singleLine) result.AppendLine(); @@ -523,18 +521,17 @@ private Size GetSize() private string GetTypeName() { if (imageInfo.Type == ImageInfoType.Icon) - return typeof(Icon).Name; - Image img = GetCurrentImage().Image; - return img?.GetType().Name ?? typeof(Bitmap).Name; + return nameof(System.Drawing.Icon); + Image? img = GetCurrentImage().Image; + return img?.GetType().Name ?? nameof(Bitmap); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Stream is passed to an Image/Icon")] private void FromFile(string fileName) { var stream = new MemoryStream(File.ReadAllBytes(fileName)); bool appearsIcon = Path.GetExtension(fileName).Equals(".ico", StringComparison.OrdinalIgnoreCase); - Icon icon = null; + Icon? icon = null; // icon is allowed and the content seems to be an icon // (this block is needed only for Windows XP: Icon Bitmap with PNG throws an exception but initializing from icon will succeed) @@ -554,7 +551,8 @@ private void FromFile(string fileName) } } - Image image = null; + Image? image = null; + string? openedFileName = fileName; // bitmaps and metafiles are both allowed if ((imageTypes & (AllowedImageTypes.Bitmap | AllowedImageTypes.Metafile)) == (AllowedImageTypes.Bitmap | AllowedImageTypes.Metafile)) @@ -595,7 +593,7 @@ private void FromFile(string fileName) if (image.RawFormat.Guid == ImageFormat.MemoryBmp.Guid) { Notification = Res.NotificationMetafileAsBitmap; - fileName = null; + openedFileName = null; } } @@ -623,7 +621,7 @@ private void FromFile(string fileName) icon = iconImage.ToIcon(); iconImage.Dispose(); Notification = Res.NotificationImageAsIcon; - fileName = null; + openedFileName = null; } } @@ -637,12 +635,12 @@ private void FromFile(string fileName) Image = image; // null will be assigned if the image has been converted (see notifications) - imageInfo.FileName = fileName; + imageInfo.FileName = openedFileName; } private Image LoadImage(MemoryStream stream) { - if (TryLoadCustom(stream, out Image image)) + if (TryLoadCustom(stream, out Image? image)) return image; // bitmaps and metafiles are both allowed @@ -666,7 +664,7 @@ private void ResetCompoundState() IsAutoPlaying = false; NextImageCommandState.Enabled = true; PrevImageCommandState.Enabled = false; - PreviewImage = imageInfo.Frames[0].Image; + PreviewImage = imageInfo.Frames![0].Image; ImageChanged(); return; } @@ -676,7 +674,7 @@ private void ResetCompoundState() bool autoPlaying = imageInfo.Type == ImageInfoType.Animation; ICommandState timerState = AdvanceAnimationCommandState; IsAutoPlaying = autoPlaying; - PreviewImage = imageInfo.Frames[0].Image; + PreviewImage = imageInfo.Frames![0].Image; if (autoPlaying) { currentFrame = 0; @@ -706,7 +704,9 @@ private void UpdateMultiResImage() // but that requires always a new bitmap and does not work in Windows XP desiredSize = Math.Max(desiredSize, 1); float zoom = AutoZoom ? 1f : Zoom; - ImageFrameInfo desiredImage = imageInfo.Frames.Aggregate((acc, i) => i.Size == acc.Size && i.BitsPerPixel > acc.BitsPerPixel || Math.Abs(i.Size.Width * zoom - desiredSize) < Math.Abs(acc.Size.Width * zoom - desiredSize) ? i : acc); + ImageFrameInfo desiredImage = imageInfo.Frames!.Aggregate((acc, i) + => i.Size == acc.Size && i.BitsPerPixel > acc.BitsPerPixel + || Math.Abs(i.Size.Width * zoom - desiredSize) < Math.Abs(acc.Size.Width * zoom - desiredSize) ? i : acc); currentResolution = desiredImage.Size; if (PreviewImage != desiredImage.Image) PreviewImage = desiredImage.Image; @@ -730,13 +730,13 @@ private void SaveIcon(Stream stream) // multi image icon without raw data else if (imageInfo.HasFrames) { - using (Icon i = Icons.Combine(imageInfo.Frames.Select(f => (Bitmap)f.Image).ToArray())) + using (Icon i = Icons.Combine(imageInfo.Frames!.Select(f => (Bitmap)f.Image!).ToArray())) i.Save(stream); } // single image icon without raw data else { - using (Icon i = Icons.Combine((Bitmap)imageInfo.Image)) + using (Icon i = Icons.Combine((Bitmap)imageInfo.Image!)) i.Save(stream); } @@ -747,23 +747,22 @@ private void SaveIcon(Stream stream) // saving a single icon image if (imageInfo.Icon != null) { - using (Icon i = imageInfo.Icon.ExtractIcon(currentFrame)) + using (Icon i = imageInfo.Icon.ExtractIcon(currentFrame)!) i.Save(stream); } else { - using (Icon i = Icons.Combine((Bitmap)GetCurrentImage().Image)) + using (Icon i = Icons.Combine((Bitmap)GetCurrentImage().Image!)) i.Save(stream); } stream.Flush(); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "False alarm, bmp is disposed if it differs from image")] private void SaveBitmapData(string fileName) { using Stream stream = File.Create(fileName); - Image image = IsCompoundView ? imageInfo.GetCreateImage() : GetCurrentImage().Image; + Image image = IsCompoundView ? imageInfo.GetCreateImage()! : GetCurrentImage().Image!; Bitmap bmp = image as Bitmap ?? new Bitmap(image); try { @@ -777,8 +776,6 @@ private void SaveBitmapData(string fileName) } } - [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", - Justification = "This is not normalization, we want to display the extensions in lowercase")] private void SetOpenFilter() { if (isOpenFilterUpToDate || imageTypes == AllowedImageTypes.None) @@ -793,10 +790,10 @@ private void SetOpenFilter() if (sb.Length != 0) sb.Append('|'); - sb.Append($"{codecInfo.FormatDescription} {Res.TextFiles}|{codecInfo.FilenameExtension.ToLowerInvariant()}"); + sb.Append($"{codecInfo.FormatDescription} {Res.TextFiles}|{codecInfo.FilenameExtension?.ToLowerInvariant()}"); if (sbImages.Length != 0) sbImages.Append(';'); - sbImages.Append(codecInfo.FilenameExtension.ToLowerInvariant()); + sbImages.Append(codecInfo.FilenameExtension?.ToLowerInvariant()); } if ((imageTypes & AllowedImageTypes.Bitmap) != AllowedImageTypes.None) @@ -809,10 +806,6 @@ private void SetOpenFilter() isOpenFilterUpToDate = true; } - [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", - Justification = "This is not normalization, we want to set the default extension in lowercase")] - [SuppressMessage("Globalization", "CA1307:Specify StringComparison", - Justification = "IndexOf(char) uses ordinal lookup and the StringComparison overload is not available on every platform")] private void SetSaveFilter() { #region Local Methods @@ -828,12 +821,12 @@ static string GetFirstExtension(string extensions) #endregion // enlisting encoders - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); foreach (ImageCodecInfo codecInfo in encoderCodecs) { if (sb.Length != 0) sb.Append('|'); - sb.Append($"{codecInfo.FormatDescription} {Res.TextFileFormat}|{codecInfo.FilenameExtension.ToLowerInvariant()}"); + sb.Append($"{codecInfo.FormatDescription} {Res.TextFileFormat}|{codecInfo.FilenameExtension?.ToLowerInvariant()}"); } bool isEmf = false; @@ -850,7 +843,7 @@ static string GetFirstExtension(string extensions) string filter = sb.ToString(); // selecting appropriate format - string ext = null; + string? ext = null; if (imageInfo.IsMultiRes) ext = "ico"; else if (imageInfo.IsMetafile) @@ -864,7 +857,7 @@ static string GetFirstExtension(string extensions) { if (encoder.FormatID == imageInfo.RawFormat) { - ext = GetFirstExtension(encoder.FilenameExtension); + ext = GetFirstExtension(encoder.FilenameExtension ?? String.Empty); found = true; break; } @@ -877,7 +870,7 @@ static string GetFirstExtension(string extensions) if (!found) { ext = isPngSupported ? "png" - : encoderCodecs.Length > 0 ? GetFirstExtension(encoderCodecs[0].FilenameExtension) + : encoderCodecs.Length > 0 ? GetFirstExtension(encoderCodecs[0].FilenameExtension ?? String.Empty) : "ico"; } } @@ -950,13 +943,13 @@ private void InvalidateImage() imageInfo.Icon = null; } - UpdatePreviewImageCallback.Invoke(); + UpdatePreviewImageCallback?.Invoke(); } private bool CheckSaveExtension(string fileName) { - string actualExt = Path.GetExtension(fileName)?.ToUpperInvariant(); - string[] filters = SaveFileFilter.Split('|'); + string actualExt = Path.GetExtension(fileName).ToUpperInvariant(); + string[] filters = SaveFileFilter!.Split('|'); int filterIndex = SaveFileFilterIndex; string suggestedExt = filters[((filterIndex - 1) << 1) + 1].ToUpperInvariant(); if (suggestedExt.Split(';').Contains('*' + actualExt)) @@ -964,7 +957,7 @@ private bool CheckSaveExtension(string fileName) return Confirm(Res.ConfirmMessageSaveFileExtension(Path.GetFileName(fileName), filters[(filterIndex - 1) << 1])); } - private void SetCurrentImage(Bitmap image) + private void SetCurrentImage(Bitmap? image) { // replacing the whole image (non-compound one) if (GetCurrentImage() == imageInfo) @@ -979,7 +972,7 @@ private void SetCurrentImage(Bitmap image) else { Debug.Assert(currentFrame >= 0 && !IsAutoPlaying); - ImageFrameInfo[] frames = imageInfo.Frames; + ImageFrameInfo[] frames = imageInfo.Frames!; ImageFrameInfo origFrame = frames[currentFrame]; frames[currentFrame] = new ImageFrameInfo(image) { Duration = origFrame.Duration }; if (!ReferenceEquals(origFrame.Image, image)) @@ -991,14 +984,14 @@ private void SetCurrentImage(Bitmap image) ImageChanged(); } - private void EditBitmap(Func> createViewModel) + private void EditBitmap(Func> createViewModel) { Debug.Assert(imageInfo.Type != ImageInfoType.None && !imageInfo.IsMetafile, "Non-metafile image is expected"); ImageInfoBase image = GetCurrentImage(); Debug.Assert(image.Image is Bitmap, "Existing bitmap image is expected"); - using (IViewModel viewModel = createViewModel.Invoke((Bitmap)image.Image)) + using (IViewModel viewModel = createViewModel.Invoke((Bitmap)image.Image!)) { ShowChildViewCallback?.Invoke(viewModel); if (viewModel.IsModified) @@ -1006,7 +999,6 @@ private void EditBitmap(Func> createViewModel) } } - [SuppressMessage("Reliability", "CA2002:Do not lock on objects with weak identity", Justification = "False alarm, image is not a remote object")] private void RotateBitmap(RotateFlipType direction) { Debug.Assert(imageInfo.Type != ImageInfoType.None && !imageInfo.IsMetafile, "Non-metafile image is expected"); @@ -1014,7 +1006,7 @@ private void RotateBitmap(RotateFlipType direction) Debug.Assert(image.Image is Bitmap, "Existing bitmap image is expected"); // must be in a lock because it can be in use in the UI (where it is also locked) - lock (image.Image) + lock (image.Image!) image.Image.RotateFlip(direction); SetCurrentImage((Bitmap)image.Image); } @@ -1023,10 +1015,10 @@ private void RotateBitmap(RotateFlipType direction) #region Explicitly Implemented Interface Methods - Image IViewModel.GetEditedModel() => (Image)Image?.Clone(); - Icon IViewModel.GetEditedModel() => Icon?.Clone() as Icon; - Bitmap IViewModel.GetEditedModel() => Image?.Clone() as Bitmap; - Metafile IViewModel.GetEditedModel() => Image?.Clone() as Metafile; + Image? IViewModel.GetEditedModel() => Image?.Clone() as Image; + Icon? IViewModel.GetEditedModel() => Icon?.Clone() as Icon; + Bitmap? IViewModel.GetEditedModel() => Image?.Clone() as Bitmap; + Metafile? IViewModel.GetEditedModel() => Image?.Clone() as Metafile; ImageInfo IViewModel.GetEditedModel() => imageTypes == AllowedImageTypes.Icon ? imageInfo.AsIcon() : imageInfo.AsImage(); #endregion @@ -1053,7 +1045,7 @@ private void OnSaveFileCommand() return; SetSaveFilter(); - string fileName; + string? fileName; do { fileName = SelectFileToSaveCallback?.Invoke(); @@ -1062,7 +1054,7 @@ private void OnSaveFileCommand() } while (!CheckSaveExtension(fileName)); int filterIndex = SaveFileFilterIndex; - string selectedFormat = SaveFileFilter.Split('|')[((filterIndex - 1) << 1) + 1]; + string selectedFormat = SaveFileFilter!.Split('|')[((filterIndex - 1) << 1) + 1]; SaveFile(fileName, selectedFormat); } @@ -1082,7 +1074,7 @@ private void OnAdvanceAnimationCommand() // playing with duration Debug.Assert(imageInfo.HasFrames); currentFrame++; - ImageFrameInfo[] frames = imageInfo.Frames; + ImageFrameInfo[] frames = imageInfo.Frames!; if (currentFrame >= frames.Length) currentFrame = 0; AdvanceAnimationCommandState[stateInterval] = frames[currentFrame].Duration; @@ -1094,7 +1086,7 @@ private void OnPrevImageCommand() if (!imageInfo.HasFrames || currentFrame <= 0) return; - PreviewImage = imageInfo.Frames[--currentFrame].Image; + PreviewImage = imageInfo.Frames![--currentFrame].Image; PrevImageCommandState.Enabled = currentFrame > 0; NextImageCommandState.Enabled = true; ImageChanged(); @@ -1102,7 +1094,7 @@ private void OnPrevImageCommand() private void OnNextImageCommand() { - ImageFrameInfo[] frames = imageInfo.Frames; + ImageFrameInfo[] frames = imageInfo.Frames!; if (!imageInfo.HasFrames || currentFrame >= frames.Length) return; @@ -1115,7 +1107,7 @@ private void OnNextImageCommand() private void OnShowPaletteCommand() { ImageInfoBase currentImage = GetCurrentImage(); - if (currentImage == null || currentImage.Palette.Length == 0) + if (currentImage.Palette.Length == 0) return; using (IViewModel vmPalette = ViewModelFactory.FromPalette(currentImage.Palette, IsPaletteReadOnly)) @@ -1125,7 +1117,7 @@ private void OnShowPaletteCommand() return; // apply changes - ColorPalette palette = currentImage.Image.Palette; + ColorPalette palette = currentImage.Image!.Palette; Color[] newPalette = vmPalette.GetEditedModel(); // even if the length of the palette is not edited it can happen that the preview image is ARGB32 @@ -1160,7 +1152,7 @@ private void OnCountColorsCommand() ImageInfoBase image = GetCurrentImage(); Debug.Assert(image.Image is Bitmap, "Existing bitmap image is expected"); - using IViewModel viewModel = ViewModelFactory.CreateCountColors((Bitmap)image.Image); + using IViewModel viewModel = ViewModelFactory.CreateCountColors((Bitmap)image.Image!); ShowChildViewCallback?.Invoke(viewModel); // this prevents the viewModel from disposing until before the view is completely finished (on cancel, for example) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs index 14c0634..cc25714 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs @@ -22,7 +22,9 @@ using System.Linq; using KGySoft.ComponentModel; -using KGySoft.CoreLibraries; +#if NETFRAMEWORK +using KGySoft.CoreLibraries; +#endif using KGySoft.Drawing.ImagingTools.Model; #endregion @@ -41,7 +43,7 @@ internal class ManageInstallationsViewModel : ViewModelBase #region Fields - private InstallationInfo currentStatus; + private InstallationInfo currentStatus = default!; private bool isSelectingPath; #endregion @@ -49,12 +51,12 @@ internal class ManageInstallationsViewModel : ViewModelBase #region Properties internal IList> Installations { get => Get>>(); set => Set(value); } - internal string SelectedInstallation { get => Get(); set => Set(value); } - internal string CurrentPath { get => Get(); set => Set(value); } + internal string SelectedInstallation { get => Get(String.Empty); set => Set(value); } + internal string CurrentPath { get => Get(String.Empty); set => Set(value); } internal string StatusText { get => Get("-"); set => Set(value); } internal string AvailableVersionText { get => Get("-"); set => Set(value); } - internal Func SelectFolderCallback { get => Get>(); set => Set(value); } + internal Func? SelectFolderCallback { get => Get?>(); set => Set(value); } internal ICommandState SelectFolderCommandState => Get(() => new CommandState()); internal ICommandState InstallCommandState => Get(() => new CommandState()); @@ -68,7 +70,7 @@ internal class ManageInstallationsViewModel : ViewModelBase #region Constructors - internal ManageInstallationsViewModel(string hintPath) + internal ManageInstallationsViewModel(string? hintPath) { InitAvailableVersion(); InitInstallations(); @@ -86,12 +88,12 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) base.OnPropertyChanged(e); if (e.PropertyName == nameof(SelectedInstallation)) { - UpdatePath((string)e.NewValue); + UpdatePath((string)e.NewValue!); return; } if (e.PropertyName == nameof(CurrentPath)) - UpdateStatus((string)e.NewValue); + UpdateStatus((string)e.NewValue!); } #endregion @@ -113,11 +115,11 @@ private void InitAvailableVersion() InstallCommandState.Enabled = available; } - private void TrySelectPath(string hintPath) + private void TrySelectPath(string? hintPath) { if (hintPath?.Contains(visualStudioName, StringComparison.Ordinal) == true) { - string preferredPath = Path.GetFileName(hintPath) == visualizersDir ? Path.GetDirectoryName(hintPath) : hintPath; + string preferredPath = Path.GetFileName(hintPath) == visualizersDir ? Path.GetDirectoryName(hintPath)! : hintPath; SelectInstallation(preferredPath); return; } @@ -206,7 +208,7 @@ private void OnInstallCommand() return; #endif - InstallationManager.Install(currentStatus.Path, out string error, out string warning); + InstallationManager.Install(currentStatus.Path, out string? error, out string? warning); if (error != null) ShowError(Res.ErrorMessageInstallationFailed(error)); else if (warning != null) @@ -218,7 +220,7 @@ private void OnRemoveCommand() { if (!Confirm(Res.ConfirmMessageRemoveInstallation)) return; - InstallationManager.Uninstall(currentStatus.Path, out string error); + InstallationManager.Uninstall(currentStatus.Path, out string? error); if (error != null) ShowError(Res.ErrorMessageRemoveInstallationFailed(error)); UpdateStatus(currentStatus.Path); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/PaletteVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/PaletteVisualizerViewModel.cs index 5888b1a..0204264 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/PaletteVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/PaletteVisualizerViewModel.cs @@ -16,11 +16,11 @@ #region Usings +using System; using System.Collections.Generic; using System.Drawing; using KGySoft.ComponentModel; -using KGySoft.CoreLibraries; #endregion @@ -32,7 +32,8 @@ internal class PaletteVisualizerViewModel : ViewModelBase, IViewModel #region Internal Properties - internal Color[] Palette { get => Get(); set => Set(value.Clone()); } + // ReSharper disable once ConstantConditionalAccessQualifier - not cloning if value is null + internal Color[] Palette { get => Get(); init => Set(value?.Clone() ?? throw new ArgumentNullException(nameof(value), PublicResources.ArgumentNull)); } internal int Count { get => Get(); set => Set(value); } internal bool ReadOnly { get => Get(); set => Set(value); } @@ -53,7 +54,7 @@ internal class PaletteVisualizerViewModel : ViewModelBase, IViewModel internal override void ViewLoaded() { base.ViewLoaded(); - if (Palette.IsNullOrEmpty()) + if (Palette.Length == 0) { ReadOnly = true; ShowInfo(Res.InfoMessagePaletteEmpty); @@ -72,8 +73,8 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) // Palette -> Count if (e.PropertyName == nameof(Palette)) { - var palette = (IList)e.NewValue; - Count = palette?.Count ?? 0; + var palette = (IList)e.NewValue!; + Count = palette.Count; } } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/PreviewImageViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/PreviewImageViewModel.cs index 8ec966f..7ec4545 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/PreviewImageViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/PreviewImageViewModel.cs @@ -28,9 +28,9 @@ internal class PreviewImageViewModel : ViewModelBase { #region Properties - internal Image OriginalImage { get => Get(); set => Set(value); } - internal Image PreviewImage { get => Get(); set => Set(value); } - internal Image DisplayImage { get => Get(); set => Set(value); } + internal Image? OriginalImage { get => Get(); set => Set(value); } + internal Image? PreviewImage { get => Get(); set => Set(value); } + internal Image? DisplayImage { get => Get(); set => Set(value); } internal bool AutoZoom { get => Get(true); set => Set(value); } internal bool SmoothZooming { get => Get(true); set => Set(value); } internal bool ShowOriginal { get => Get(); set => Set(value); } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/QuantizerSelectorViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/QuantizerSelectorViewModel.cs index e4c18f9..bb48d92 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/QuantizerSelectorViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/QuantizerSelectorViewModel.cs @@ -35,10 +35,10 @@ internal class QuantizerSelectorViewModel : ViewModelBase // not a static property so always can be reinitialized with the current language internal IList Quantizers => Get(InitQuantizers); - internal QuantizerDescriptor SelectedQuantizer { get => Get(); private set => Set(value); } - internal CustomPropertiesObject Parameters { get => Get(); private set => Set(value); } - internal IQuantizer Quantizer { get => Get(); private set => Set(value); } - internal Exception CreateQuantizerError { get => Get(); set => Set(value); } + internal QuantizerDescriptor? SelectedQuantizer { get => Get(); private set => Set(value); } + internal CustomPropertiesObject? Parameters { get => Get(); private set => Set(value); } + internal IQuantizer? Quantizer { get => Get(); private set => Set(value); } + internal Exception? CreateQuantizerError { get => Get(); set => Set(value); } #endregion @@ -51,7 +51,7 @@ private static IList InitQuantizers() => { //new QuantizerDescriptor(typeof(PredefinedColorsQuantizer), nameof(PredefinedColorsQuantizer.FromPixelFormat)), new QuantizerDescriptor(typeof(PredefinedColorsQuantizer), nameof(PredefinedColorsQuantizer.BlackAndWhite)), - new QuantizerDescriptor(typeof(PredefinedColorsQuantizer).GetMethod(nameof(PredefinedColorsQuantizer.FromCustomPalette), new[] { typeof(Color[]), typeof(Color), typeof(byte) })), + new QuantizerDescriptor(typeof(PredefinedColorsQuantizer).GetMethod(nameof(PredefinedColorsQuantizer.FromCustomPalette), new[] { typeof(Color[]), typeof(Color), typeof(byte) })!), new QuantizerDescriptor(typeof(PredefinedColorsQuantizer), nameof(PredefinedColorsQuantizer.Grayscale4)), new QuantizerDescriptor(typeof(PredefinedColorsQuantizer), nameof(PredefinedColorsQuantizer.Grayscale16)), new QuantizerDescriptor(typeof(PredefinedColorsQuantizer), nameof(PredefinedColorsQuantizer.Grayscale)), @@ -78,18 +78,19 @@ private static IList InitQuantizers() => internal void ResetQuantizer() { - QuantizerDescriptor descriptor = SelectedQuantizer; + QuantizerDescriptor? descriptor = SelectedQuantizer; + CustomPropertiesObject? parameters = Parameters; CreateQuantizerError = null; - if (descriptor == null) + if (descriptor == null || parameters == null) { Quantizer = null; return; } - object[] parameters = descriptor.EvaluateParameters(Parameters); + object?[] parameterValues = descriptor.EvaluateParameters(parameters); try { - Quantizer = (IQuantizer)MethodAccessor.GetAccessor(descriptor.Method).Invoke(null, parameters); + Quantizer = (IQuantizer)MethodAccessor.GetAccessor(descriptor.Method).Invoke(null, parameterValues)!; } catch (Exception e) when (!e.IsCritical()) { @@ -105,19 +106,18 @@ internal void ResetQuantizer() protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) { base.OnPropertyChanged(e); - if (e.PropertyName == nameof(SelectedQuantizer)) + switch (e.PropertyName) { - CustomPropertiesObject previousParameters = Parameters; - Parameters = previousParameters == null - ? new CustomPropertiesObject(SelectedQuantizer.Parameters) - : new CustomPropertiesObject(previousParameters, SelectedQuantizer.Parameters); - return; - } - - if (e.PropertyName == nameof(Parameters)) - { - ResetQuantizer(); - return; + case nameof(SelectedQuantizer): + CustomPropertiesObject? previousParameters = Parameters; + Parameters = previousParameters == null + ? new CustomPropertiesObject(SelectedQuantizer!.Parameters) + : new CustomPropertiesObject(previousParameters, SelectedQuantizer!.Parameters); + return; + + case nameof(Parameters): + ResetQuantizer(); + return; } } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ResizeBitmapViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ResizeBitmapViewModel.cs index 3be9046..bf2b02e 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ResizeBitmapViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ResizeBitmapViewModel.cs @@ -17,7 +17,6 @@ #region Usings using System; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Imaging; using System.Linq; @@ -39,11 +38,11 @@ private sealed class ResizeTask : GenerateTaskBase { #region Fields - private Bitmap sourceBitmap; - private Bitmap targetBitmap; + private Bitmap? sourceBitmap; + private Bitmap? targetBitmap; private bool isSourceCloned; - private IReadableBitmapData sourceBitmapData; - private IReadWriteBitmapData targetBitmapData; + private IReadableBitmapData? sourceBitmapData; + private IReadWriteBitmapData? targetBitmapData; #endregion @@ -68,8 +67,6 @@ internal ResizeTask(Size size, ScalingMode scalingMode) #region Internal Methods - [SuppressMessage("Reliability", "CA2002:Do not lock on objects with weak identity", - Justification = "False alarm, source is never a remote object")] internal override void Initialize(Bitmap source, bool isInUse) { // this must be the first line to prevent disposing source if next lines fail @@ -102,17 +99,17 @@ internal override void Initialize(Bitmap source, bool isInUse) } internal override IAsyncResult BeginGenerate(AsyncConfig asyncConfig) - => sourceBitmapData.BeginDrawInto(targetBitmapData, new Rectangle(0, 0, sourceBitmapData.Width, sourceBitmapData.Height), - new Rectangle(0, 0, targetBitmapData.Width, targetBitmapData.Height), + => sourceBitmapData!.BeginDrawInto(targetBitmapData!, new Rectangle(0, 0, sourceBitmapData!.Width, sourceBitmapData.Height), + new Rectangle(0, 0, targetBitmapData!.Width, targetBitmapData.Height), scalingMode: ScalingMode, asyncConfig: asyncConfig); - internal override Bitmap EndGenerate(IAsyncResult asyncResult) + internal override Bitmap? EndGenerate(IAsyncResult asyncResult) { asyncResult.EndDrawInto(); // If there was no exception returning result and clearing the field to prevent disposing. // The caller will take care of disposing if the operation was canceled and the result is discarded. - Bitmap bmp = targetBitmap; + Bitmap? bmp = targetBitmap; targetBitmap = null; return bmp; } @@ -231,7 +228,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) case nameof(Width): if (!ByPixels) break; - int width = (int)e.NewValue; + int width = (int)e.NewValue!; WidthRatio = width <= 0 ? 0f : (float)width / originalSize.Width; if (!KeepAspectRatio || adjustingWidth) break; @@ -250,7 +247,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) case nameof(WidthRatio): if (!ByPercentage) break; - float widthRatio = (float)e.NewValue; + float widthRatio = (float)e.NewValue!; Width = widthRatio <= 0f ? 0 : (int)(originalSize.Width * widthRatio); if (!KeepAspectRatio || adjustingWidth) break; @@ -269,7 +266,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) case nameof(Height): if (!ByPixels) break; - int height = (int)e.NewValue; + int height = (int)e.NewValue!; HeightRatio = height <= 0 ? 0f : (float)height / originalSize.Height; if (!KeepAspectRatio || adjustingHeight) break; @@ -288,7 +285,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) case nameof(HeightRatio): if (!ByPercentage) break; - float heightRatio = (float)e.NewValue; + float heightRatio = (float)e.NewValue!; Height = heightRatio <= 0f ? 0 : (int)(originalSize.Height * heightRatio); if (!KeepAspectRatio || adjustingHeight) break; diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs index dcb2509..786e834 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs @@ -19,7 +19,6 @@ using System; using System.ComponentModel; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Threading; @@ -31,7 +30,7 @@ namespace KGySoft.Drawing.ImagingTools.ViewModel { - internal abstract class TransformBitmapViewModelBase : ViewModelBase, IViewModel, IValidatingObject + internal abstract class TransformBitmapViewModelBase : ViewModelBase, IViewModel, IValidatingObject { #region Nested Classes @@ -41,7 +40,7 @@ protected abstract class GenerateTaskBase : AsyncTaskBase internal abstract void Initialize(Bitmap source, bool isInUse); internal abstract IAsyncResult BeginGenerate(AsyncConfig asyncConfig); - internal abstract Bitmap EndGenerate(IAsyncResult asyncResult); + internal abstract Bitmap? EndGenerate(IAsyncResult asyncResult); #endregion } @@ -53,16 +52,16 @@ protected abstract class GenerateTaskBase : AsyncTaskBase private readonly Bitmap originalImage; private readonly object syncRoot = new object(); - private volatile GenerateTaskBase activeTask; + private volatile GenerateTaskBase? activeTask; private bool initializing = true; private bool keepResult; - private DrawingProgressManager drawingProgressManager; + private DrawingProgressManager? drawingProgressManager; #endregion #region Events - internal event EventHandler> ValidationResultsChanged + internal event EventHandler>? ValidationResultsChanged { add => ValidationResultsChangedHandler += value; remove => ValidationResultsChangedHandler -= value; @@ -102,8 +101,8 @@ internal event EventHandler> ValidationRe #region Private Properties - private Exception GeneratePreviewError { get => Get(); set => Set(value); } - private EventHandler> ValidationResultsChangedHandler { get => Get>>(); set => Set(value); } + private Exception? GeneratePreviewError { get => Get(); set => Set(value); } + private EventHandler>? ValidationResultsChangedHandler { get => Get>?>(); set => Set(value); } #endregion @@ -115,7 +114,7 @@ protected TransformBitmapViewModelBase(Bitmap image) { originalImage = image ?? throw new ArgumentNullException(nameof(image), PublicResources.ArgumentNull); PreviewImageViewModel previewImage = PreviewImageViewModel; - previewImage.PropertyChanged += PreviewImage_PropertyChanged; + previewImage.PropertyChanged += PreviewImage_PropertyChanged!; previewImage.PreviewImage = previewImage.OriginalImage = image; } @@ -143,7 +142,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) switch (e.PropertyName) { case nameof(ValidationResults): - var validationResults = (ValidationResultsCollection)e.NewValue; + var validationResults = (ValidationResultsCollection)e.NewValue!; IsValid = !validationResults.HasErrors; ValidationResultsChangedHandler?.Invoke(this, new EventArgs(validationResults)); return; @@ -160,7 +159,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) return; default: - if (initializing || !AffectsPreview(e.PropertyName)) + if (initializing || !AffectsPreview(e.PropertyName!)) return; Validate(); ResetCommandState.Enabled = AreSettingsChanged; @@ -178,7 +177,7 @@ protected void Validate() protected virtual ValidationResultsCollection DoValidation() { - Exception error = GeneratePreviewError; + Exception? error = GeneratePreviewError; var result = new ValidationResultsCollection(); // errors @@ -208,7 +207,7 @@ protected void BeginGeneratePreview() } IsGenerating = true; - ThreadPool.QueueUserWorkItem(DoGenerate, CreateGenerateTask()); + ThreadPool.QueueUserWorkItem(DoGenerate!, CreateGenerateTask()); } protected abstract GenerateTaskBase CreateGenerateTask(); @@ -223,8 +222,8 @@ protected override void Dispose(bool disposing) if (disposing) { activeTask?.Dispose(); - Image preview = PreviewImageViewModel.PreviewImage; - PreviewImageViewModel?.Dispose(); + Image? preview = PreviewImageViewModel.PreviewImage; + PreviewImageViewModel.Dispose(); if (!ReferenceEquals(originalImage, preview) && !keepResult) preview?.Dispose(); @@ -239,7 +238,6 @@ protected override void Dispose(bool disposing) #region Private Methods - [SuppressMessage("Reliability", "CA2002:Do not lock on objects with weak identity", Justification = "False alarm, originalImage is not a remote object")] private void DoGenerate(object state) { var task = (GenerateTaskBase)state; @@ -259,7 +257,7 @@ private void DoGenerate(object state) Debug.Assert(activeTask == null); // resetting possible previous progress - drawingProgressManager.Report(default); + drawingProgressManager?.Report(default); // from now on the task can be canceled activeTask = task; @@ -293,8 +291,8 @@ private void DoGenerate(object state) return; } - Exception error = null; - Bitmap result = null; + Exception? error = null; + Bitmap? result = null; try { // starting generate: using Begin.../End... methods instead of await ...Async so it is compatible even with .NET 3.5 @@ -343,7 +341,7 @@ private void DoGenerate(object state) private void CancelRunningGenerate() { - GenerateTaskBase runningTask = activeTask; + GenerateTaskBase? runningTask = activeTask; if (runningTask == null) return; runningTask.IsCanceled = true; @@ -352,7 +350,7 @@ private void CancelRunningGenerate() private void WaitForPendingGenerate() { // In a non-UI thread it should be in a lock - GenerateTaskBase runningTask = activeTask; + GenerateTaskBase? runningTask = activeTask; if (runningTask == null) return; runningTask.WaitForCompletion(); @@ -360,10 +358,10 @@ private void WaitForPendingGenerate() activeTask = null; } - private void SetPreview(Bitmap image) + private void SetPreview(Bitmap? image) { PreviewImageViewModel preview = PreviewImageViewModel; - Image toDispose = preview.PreviewImage; + Image? toDispose = preview.PreviewImage; preview.PreviewImage = image; if (toDispose != null && toDispose != originalImage) toDispose.Dispose(); @@ -380,7 +378,7 @@ private void PreviewImage_PropertyChanged(object sender, PropertyChangedEventArg // preview image has been changed: updating IsModified accordingly if (e.PropertyName == nameof(vm.PreviewImage)) { - Image image = vm.PreviewImage; + Image? image = vm.PreviewImage; SetModified(image != null && originalImage != image); } } @@ -411,7 +409,7 @@ private void OnApplyCommand() #region Explicitly Implemented Interface Methods - Bitmap IViewModel.GetEditedModel() => PreviewImageViewModel.PreviewImage as Bitmap; + Bitmap? IViewModel.GetEditedModel() => PreviewImageViewModel.PreviewImage as Bitmap; #endregion diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs index 6fe8703..dfe3622 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs @@ -31,13 +31,13 @@ internal abstract class ViewModelBase : ObservableObjectBase, IViewModel { #region Properties - internal Action ShowErrorCallback { get => Get>(); set => Set(value); } - internal Action ShowWarningCallback { get => Get>(); set => Set(value); } - internal Action ShowInfoCallback { get => Get>(); set => Set(value); } - internal Func ConfirmCallback { get => Get>(); set => Set(value); } - internal Action ShowChildViewCallback { get => Get>(); set => Set(value); } - internal Action CloseViewCallback { get => Get(); set => Set(value); } - internal Action SynchronizedInvokeCallback { get => Get>(); set => Set(value); } + internal Action? ShowErrorCallback { get => Get?>(); set => Set(value); } + internal Action? ShowWarningCallback { get => Get?>(); set => Set(value); } + internal Action? ShowInfoCallback { get => Get?>(); set => Set(value); } + internal Func? ConfirmCallback { get => Get?>(); set => Set(value); } + internal Action? ShowChildViewCallback { get => Get?>(); set => Set(value); } + internal Action? CloseViewCallback { get => Get(); set => Set(value); } + internal Action? SynchronizedInvokeCallback { get => Get?>(); set => Set(value); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs index 8481bb3..eea552a 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs @@ -44,7 +44,7 @@ public static class ViewModelFactory /// /// The command line arguments. /// An instance that represents a view model for the specified command line arguments. - public static IViewModel FromCommandLineArguments(string[] args) => new DefaultViewModel { CommandLineArguments = args }; + public static IViewModel FromCommandLineArguments(string[]? args) => new DefaultViewModel { CommandLineArguments = args }; /// /// Creates a view model from an . @@ -52,7 +52,8 @@ public static class ViewModelFactory /// The image. /// , to create a read-only instance; otherwise, . /// An instance that represents a view model for an . - public static IViewModel FromImage(Image image, bool readOnly = false) => new ImageVisualizerViewModel { Image = (Image)image?.Clone(), ReadOnly = readOnly }; + public static IViewModel FromImage(Image? image, bool readOnly = false) + => new ImageVisualizerViewModel { Image = (Image?)image?.Clone(), ReadOnly = readOnly }; /// /// Creates a view model for an from arbitrary debug information. @@ -60,7 +61,8 @@ public static class ViewModelFactory /// The debug information for an image. /// , to create a read-only instance; otherwise, . /// An instance that represents a view model for an . - public static IViewModel FromImage(ImageInfo imageInfo, bool readOnly) => new ImageVisualizerViewModel { ImageInfo = imageInfo, ReadOnly = readOnly }; + public static IViewModel FromImage(ImageInfo? imageInfo, bool readOnly) + => new ImageVisualizerViewModel { ImageInfo = imageInfo, ReadOnly = readOnly }; /// /// Creates a view model from a . @@ -68,7 +70,8 @@ public static class ViewModelFactory /// The bitmap. /// , to create a read-only instance; otherwise, . /// An instance that represents a view model for a . - public static IViewModel FromBitmap(Bitmap bitmap, bool readOnly = false) => new ImageVisualizerViewModel(AllowedImageTypes.Bitmap | AllowedImageTypes.Icon) { Image = (Bitmap)bitmap?.Clone(), ReadOnly = readOnly }; + public static IViewModel FromBitmap(Bitmap? bitmap, bool readOnly = false) + => new ImageVisualizerViewModel(AllowedImageTypes.Bitmap | AllowedImageTypes.Icon) { Image = (Bitmap?)bitmap?.Clone(), ReadOnly = readOnly }; /// /// Creates a view model for a from arbitrary debug information. @@ -76,7 +79,8 @@ public static class ViewModelFactory /// The debug information for a bitmap. /// , to create a read-only instance; otherwise, . /// An instance that represents a view model for a . - public static IViewModel FromBitmap(ImageInfo bitmapInfo, bool readOnly) => new ImageVisualizerViewModel(AllowedImageTypes.Bitmap | AllowedImageTypes.Icon) { ImageInfo = bitmapInfo, ReadOnly = readOnly }; + public static IViewModel FromBitmap(ImageInfo? bitmapInfo, bool readOnly) + => new ImageVisualizerViewModel(AllowedImageTypes.Bitmap | AllowedImageTypes.Icon) { ImageInfo = bitmapInfo, ReadOnly = readOnly }; /// /// Creates a view model from a . @@ -84,7 +88,8 @@ public static class ViewModelFactory /// The metafile. /// , to create a read-only instance; otherwise, . /// An instance that represents a view model for a . - public static IViewModel FromMetafile(Metafile metafile, bool readOnly = false) => new ImageVisualizerViewModel(AllowedImageTypes.Metafile) { Image = (Metafile)metafile?.Clone(), ReadOnly = readOnly }; + public static IViewModel FromMetafile(Metafile? metafile, bool readOnly = false) + => new ImageVisualizerViewModel(AllowedImageTypes.Metafile) { Image = (Metafile?)metafile?.Clone(), ReadOnly = readOnly }; /// /// Creates a view model for a from arbitrary debug information. @@ -92,7 +97,8 @@ public static class ViewModelFactory /// The debug information for a metafile. /// , to create a read-only instance; otherwise, . /// An instance that represents a view model for a . - public static IViewModel FromMetafile(ImageInfo metafileInfo, bool readOnly) => new ImageVisualizerViewModel(AllowedImageTypes.Metafile) { ImageInfo = metafileInfo, ReadOnly = readOnly }; + public static IViewModel FromMetafile(ImageInfo? metafileInfo, bool readOnly) + => new ImageVisualizerViewModel(AllowedImageTypes.Metafile) { ImageInfo = metafileInfo, ReadOnly = readOnly }; /// /// Creates a view model from an . @@ -100,7 +106,8 @@ public static class ViewModelFactory /// The icon. /// , to create a read-only instance; otherwise, . /// An instance that represents a view model for an . - public static IViewModel FromIcon(Icon icon, bool readOnly = false) => new ImageVisualizerViewModel(AllowedImageTypes.Icon) { Icon = (Icon)icon?.Clone(), ReadOnly = readOnly }; + public static IViewModel FromIcon(Icon? icon, bool readOnly = false) + => new ImageVisualizerViewModel(AllowedImageTypes.Icon) { Icon = (Icon?)icon?.Clone(), ReadOnly = readOnly }; /// /// Creates a view model for an from arbitrary debug information. @@ -108,7 +115,8 @@ public static class ViewModelFactory /// The debug information for an icon. /// , to create a read-only instance; otherwise, . /// An instance that represents a view model for an . - public static IViewModel FromIcon(ImageInfo iconInfo, bool readOnly) => new ImageVisualizerViewModel(AllowedImageTypes.Icon) { ImageInfo = iconInfo, ReadOnly = readOnly }; + public static IViewModel FromIcon(ImageInfo? iconInfo, bool readOnly) + => new ImageVisualizerViewModel(AllowedImageTypes.Icon) { ImageInfo = iconInfo, ReadOnly = readOnly }; /// /// Creates a view model from a palette. @@ -132,35 +140,37 @@ public static class ViewModelFactory /// If the provided path is among the detected Visual Studio installations, then it will be preselected in the view. This parameter is optional. ///
Default value: . /// An instance that represents a view model for managing debugger visualizer installations. - public static IViewModel CreateManageInstallations(string hintPath = null) => new ManageInstallationsViewModel(hintPath); + public static IViewModel CreateManageInstallations(string? hintPath = null) => new ManageInstallationsViewModel(hintPath); /// /// Creates a view model from a . /// /// The bitmap data. /// An instance that represents a view model for a . - public static IViewModel FromBitmapData(BitmapData bitmapData) => new BitmapDataVisualizerViewModel { BitmapDataInfo = new BitmapDataInfo(bitmapData) }; + public static IViewModel FromBitmapData(BitmapData? bitmapData) + => new BitmapDataVisualizerViewModel { BitmapDataInfo = bitmapData == null ? null : new BitmapDataInfo(bitmapData) }; /// /// Creates a view model for a from arbitrary debug information. /// /// The debug information for a . /// An instance that represents a view model for a . - public static IViewModel FromBitmapData(BitmapDataInfo bitmapDataInfo) => new BitmapDataVisualizerViewModel { BitmapDataInfo = bitmapDataInfo }; + public static IViewModel FromBitmapData(BitmapDataInfo? bitmapDataInfo) => new BitmapDataVisualizerViewModel { BitmapDataInfo = bitmapDataInfo }; /// /// Creates a view model from a . /// /// The graphics. /// An instance that represents a view model for a . - public static IViewModel FromGraphics(Graphics graphics) => new GraphicsVisualizerViewModel { GraphicsInfo = new GraphicsInfo(graphics)}; + public static IViewModel FromGraphics(Graphics? graphics) + => new GraphicsVisualizerViewModel { GraphicsInfo = graphics == null ? null : new GraphicsInfo(graphics)}; /// /// Creates a view model for a from arbitrary debug information. /// /// The debug information for a . /// An instance that represents a view model for a . - public static IViewModel FromGraphics(GraphicsInfo graphicsInfo) => new GraphicsVisualizerViewModel { GraphicsInfo = graphicsInfo }; + public static IViewModel FromGraphics(GraphicsInfo? graphicsInfo) => new GraphicsVisualizerViewModel { GraphicsInfo = graphicsInfo }; /// /// Creates a view model for counting colors of a . @@ -174,35 +184,35 @@ public static class ViewModelFactory /// /// The bitmap to resize. /// An instance that represents a view model for resizing a . - public static IViewModel CreateResizeBitmap(Bitmap bitmap) => new ResizeBitmapViewModel(bitmap); + public static IViewModel CreateResizeBitmap(Bitmap bitmap) => new ResizeBitmapViewModel(bitmap); /// /// Creates a view model for adjusting of a with quantizing and dithering. /// /// The bitmap to transform. /// An instance that represents a view model for adjusting the colors of a . - public static IViewModel CreateAdjustColorSpace(Bitmap bitmap) => new ColorSpaceViewModel(bitmap); + public static IViewModel CreateAdjustColorSpace(Bitmap bitmap) => new ColorSpaceViewModel(bitmap); /// /// Creates a view model for adjusting the brightness of a . /// /// The bitmap to adjust. /// An instance that represents a view model for adjusting the brightness of a . - public static IViewModel CreateAdjustBrightness(Bitmap bitmap) => new AdjustBrightnessViewModel(bitmap); + public static IViewModel CreateAdjustBrightness(Bitmap bitmap) => new AdjustBrightnessViewModel(bitmap); /// /// Creates a view model for adjusting the contrast of a . /// /// The bitmap to adjust. /// An instance that represents a view model for adjusting the contrast of a . - public static IViewModel CreateAdjustContrast(Bitmap bitmap) => new AdjustContrastViewModel(bitmap); + public static IViewModel CreateAdjustContrast(Bitmap bitmap) => new AdjustContrastViewModel(bitmap); /// /// Creates a view model for adjusting the gamma of a . /// /// The bitmap to adjust. /// An instance that represents a view model for adjusting the gamma of a . - public static IViewModel CreateAdjustGamma(Bitmap bitmap) => new AdjustGammaViewModel(bitmap); + public static IViewModel CreateAdjustGamma(Bitmap bitmap) => new AdjustGammaViewModel(bitmap); #endregion } diff --git a/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs b/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs index 4d7c200..79cadad 100644 --- a/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs +++ b/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs @@ -19,6 +19,7 @@ namespace KGySoft.Drawing.ImagingTools.WinApi internal static class Constants { #region Constants + // ReSharper disable InconsistentNaming internal const int WS_BORDER = 0x00800000; internal const int WM_MOUSEHWHEEL = 0x020E; @@ -27,6 +28,7 @@ internal static class Constants internal const int MA_ACTIVATEANDEAT= 2; internal const int MA_ACTIVATE = 1; + // ReSharper restore InconsistentNaming #endregion } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/WinApi/MEMORYSTATUSEX.cs b/KGySoft.Drawing.ImagingTools/WinApi/MEMORYSTATUSEX.cs index fbf64ac..b311259 100644 --- a/KGySoft.Drawing.ImagingTools/WinApi/MEMORYSTATUSEX.cs +++ b/KGySoft.Drawing.ImagingTools/WinApi/MEMORYSTATUSEX.cs @@ -23,6 +23,7 @@ namespace KGySoft.Drawing.ImagingTools.WinApi { [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming internal struct MEMORYSTATUSEX { #region Fields diff --git a/KGySoft.Drawing.ImagingTools/_Classes/DrawingProgressManager.cs b/KGySoft.Drawing.ImagingTools/_Classes/DrawingProgressManager.cs index eb6b0b7..907525a 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/DrawingProgressManager.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/DrawingProgressManager.cs @@ -35,10 +35,7 @@ internal class DrawingProgressManager : IDrawingProgress #region Constructors - internal DrawingProgressManager(Action reportCallback) - { - this.reportCallback = reportCallback; - } + internal DrawingProgressManager(Action reportCallback) => this.reportCallback = reportCallback; #endregion diff --git a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs index 43436c5..75b60a2 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs @@ -22,7 +22,9 @@ using KGySoft.CoreLibraries; using KGySoft.Drawing.ImagingTools.Model; +#if NET45 using KGySoft.Drawing.ImagingTools.WinApi; +#endif #endregion @@ -50,7 +52,7 @@ public static class InstallationManager debuggerVisualizerFileName }; - private static InstallationInfo availableVersion; + private static InstallationInfo? availableVersion; #endregion @@ -77,7 +79,7 @@ public static class InstallationManager /// The directory where the debugger visualizers have to be installed. /// If the installation fails, then this parameter returns the error message; otherwise, this parameter returns . /// If the installation succeeds with warnings, then this parameter returns the warning message; otherwise, this parameter returns . - public static void Install(string directory, out string error, out string warning) + public static void Install(string directory, out string? error, out string? warning) { if (directory == null) throw new ArgumentNullException(nameof(directory), PublicResources.ArgumentNull); @@ -187,7 +189,7 @@ public static void Install(string directory, out string error, out string warnin ///
/// The directory where the debugger visualizers have to be removed from. /// If the removal fails, then this parameter returns the error message; otherwise, this parameter returns . - public static void Uninstall(string directory, out string error) + public static void Uninstall(string directory, out string? error) { error = null; if (!Directory.Exists(directory)) diff --git a/KGySoft.Drawing.ImagingTools/_Extensions/CommandBindingsCollectionExtensions.cs b/KGySoft.Drawing.ImagingTools/_Extensions/CommandBindingsCollectionExtensions.cs index 25b7f23..1999e48 100644 --- a/KGySoft.Drawing.ImagingTools/_Extensions/CommandBindingsCollectionExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/_Extensions/CommandBindingsCollectionExtensions.cs @@ -41,7 +41,7 @@ internal static class CommandBindingsCollectionExtensions #region Fields - private static ICommand propertyChangedCommand = new SourceAwareCommand(OnPropertyChangedCommand); + private static readonly ICommand propertyChangedCommand = new SourceAwareCommand(OnPropertyChangedCommand); #endregion @@ -50,7 +50,7 @@ internal static class CommandBindingsCollectionExtensions #region Internal Methods internal static void AddTwoWayPropertyBinding(this CommandBindingsCollection collection, object source, string sourcePropertyName, object target, - string targetPropertyName = null, Func format = null, Func parse = null) + string? targetPropertyName = null, Func? format = null, Func? parse = null) { collection.AddPropertyBinding(source, sourcePropertyName, targetPropertyName ?? sourcePropertyName, format, target); collection.AddPropertyBinding(target, targetPropertyName ?? sourcePropertyName, sourcePropertyName, parse, source); @@ -63,7 +63,7 @@ internal static ICommandBinding AddPropertyChangedHandler(this CommandBindingsCo if (propertyNames == null) throw new ArgumentNullException(nameof(propertyNames)); - var state = new Dictionary + var state = new Dictionary { [stateHandler] = handler, [statePropertyNames] = propertyNames @@ -81,7 +81,7 @@ private static void OnPropertyChangedCommand(ICommandSource(statePropertyNames))) return; - state.GetValueOrDefault(stateHandler)?.Invoke(); + state.GetValueOrDefault(stateHandler)?.Invoke(); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/_Extensions/PixelFormatExtensions.cs b/KGySoft.Drawing.ImagingTools/_Extensions/PixelFormatExtensions.cs index 9e0b7ff..425eb6e 100644 --- a/KGySoft.Drawing.ImagingTools/_Extensions/PixelFormatExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/_Extensions/PixelFormatExtensions.cs @@ -27,9 +27,12 @@ internal static class PixelFormatExtensions #region Methods internal static bool CanBeDithered(this PixelFormat dstFormat) => dstFormat.ToBitsPerPixel() <= 16 && dstFormat != PixelFormat.Format16bppGrayScale; + + // ReSharper disable BitwiseOperatorOnEnumWithoutFlags internal static bool HasAlpha(this PixelFormat pixelFormat) => (pixelFormat & PixelFormat.Alpha) == PixelFormat.Alpha; internal static bool IsIndexed(this PixelFormat pixelFormat) => (pixelFormat & PixelFormat.Indexed) == PixelFormat.Indexed; internal static bool IsWide(this PixelFormat pixelFormat) => (pixelFormat & PixelFormat.Extended) == PixelFormat.Extended; + // ReSharper restore BitwiseOperatorOnEnumWithoutFlags #endregion } diff --git a/KGySoft.Drawing.Tools.sln.DotSettings b/KGySoft.Drawing.Tools.sln.DotSettings index 9152776..7e3d7c9 100644 --- a/KGySoft.Drawing.Tools.sln.DotSettings +++ b/KGySoft.Drawing.Tools.sln.DotSettings @@ -1,5 +1,9 @@  + HINT + SUGGESTION DO_NOT_SHOW + HINT + HINT True True NEVER @@ -9,10 +13,17 @@ UseVarWhenEvident UseVarWhenEvident UseVarWhenEvident + GC + NC + OS + UI + VM <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> True True True - True \ No newline at end of file + True + True + True \ No newline at end of file From 366cf687b69c1b5e40666304cb8808db2903c082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 30 Apr 2021 18:42:21 +0200 Subject: [PATCH 003/211] Debugger visualizers and tester: enabling nullable references --- .../GlobalSuppressions.cs | 1 + ...ft.Drawing.DebuggerVisualizers.Test.csproj | 9 +- .../Program.cs | 3 - .../View/DebuggerTestForm.cs | 9 +- .../ViewModel/DebuggerTestFormViewModel.cs | 97 ++++++++++--------- .../ViewModel/TestObjectProvider.cs | 14 ++- .../_Classes/OSUtils.cs | 1 - .../DebuggerHelper.cs | 28 +++--- .../GlobalSuppressions.cs | 1 + ...KGySoft.Drawing.DebuggerVisualizers.csproj | 7 +- .../Model/ColorPaletteReference.cs | 20 ++-- .../Model/ColorReference.cs | 9 +- .../Model/ImageReference.cs | 33 ++++--- .../BitmapDataSerializationInfo.cs | 11 +-- .../ColorPaletteSerializationInfo.cs | 6 +- .../Serialization/ColorSerializationInfo.cs | 3 - .../GraphicsSerializationInfo.cs | 13 +-- .../Serialization/ImageSerializationInfo.cs | 25 +++-- .../Serialization/SerializationHelper.cs | 29 +----- .../BitmapDebuggerVisualizer.cs | 2 +- .../ColorPaletteDebuggerVisualizer.cs | 2 +- .../IconDebuggerVisualizer.cs | 2 +- .../ImageDebuggerVisualizer.cs | 2 +- .../MetafileDebuggerVisualizer.cs | 2 +- .../KGySoft.Drawing.ImagingTools.csproj | 2 +- KGySoft.Drawing.ImagingTools/Res.cs | 1 - 26 files changed, 155 insertions(+), 177 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/GlobalSuppressions.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/GlobalSuppressions.cs index 4c4658c..bfa5186 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/GlobalSuppressions.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/GlobalSuppressions.cs @@ -7,3 +7,4 @@ [assembly: SuppressMessage("Style", "IDE0063:Use simple 'using' statement", Justification = "Decided individually")] [assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "Decided individually")] +[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "Decided individually")] diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj b/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj index ea1500c..a285c34 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj @@ -1,7 +1,7 @@  - + net45 false KGySoft.Drawing.DebuggerVisualizers.Test @@ -16,6 +16,7 @@ true WinExe app.manifest + enable @@ -25,7 +26,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + NU1701 + @@ -33,7 +36,7 @@ - + diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/Program.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/Program.cs index aa1f12a..8b4a7cd 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/Program.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/Program.cs @@ -17,7 +17,6 @@ #region Usings using System; -using System.Diagnostics.CodeAnalysis; using System.Windows.Forms; using KGySoft.Drawing.DebuggerVisualizers.Test.View; @@ -31,8 +30,6 @@ static class Program #region Methods [STAThread] - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "Would just cause double disposing because closing will dispose the form anyway.")] static void Main() { Application.EnableVisualStyles(); diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.cs index 5d7be94..84b69a8 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.cs @@ -17,7 +17,6 @@ #region Usings using System; -using System.Diagnostics.CodeAnalysis; using System.Windows.Forms; using KGySoft.ComponentModel; @@ -34,7 +33,8 @@ public partial class DebuggerTestForm : Form private readonly CommandBindingsCollection commandBindings = new CommandBindingsCollection(); private readonly DebuggerTestFormViewModel viewModel = new DebuggerTestFormViewModel(); private readonly Timer timer; - private string errorMessage; + + private string? errorMessage; #endregion @@ -113,7 +113,7 @@ protected override void Dispose(bool disposing) components?.Dispose(); commandBindings.Dispose(); viewModel.Dispose(); - timer?.Dispose(); + timer.Dispose(); } base.Dispose(disposing); @@ -128,14 +128,13 @@ private void OnSelectFileCommand(ICommandSource source) // simple click opens the file dialog only if text was empty if (tbFile.Text.Length != 0 && source.TriggeringEvent == nameof(tbFile.Click)) return; - using (OpenFileDialog ofd = new OpenFileDialog { FileName = tbFile.Text }) + using (var ofd = new OpenFileDialog { FileName = tbFile.Text }) { if (ofd.ShowDialog() == DialogResult.OK) tbFile.Text = ofd.FileName; } } - [SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "This is just a test app")] private void OnShowErrorCommand() { timer.Enabled = false; diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/DebuggerTestFormViewModel.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/DebuggerTestFormViewModel.cs index ad59478..585d88b 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/DebuggerTestFormViewModel.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/DebuggerTestFormViewModel.cs @@ -19,7 +19,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; @@ -62,7 +61,7 @@ internal class DebuggerTestFormViewModel : ObservableObjectBase }; private static readonly Dictionary debuggerVisualizers = Attribute.GetCustomAttributes(typeof(DebuggerHelper).Assembly, typeof(DebuggerVisualizerAttribute)) - .Cast().ToDictionary(a => a.Target, a => a); + .Cast().ToDictionary(a => a.Target!, a => a); #endregion @@ -70,7 +69,13 @@ internal class DebuggerTestFormViewModel : ObservableObjectBase internal bool AsImage { get => Get(); set => Set(value); } internal bool AsImageEnabled { get => Get(); set => Set(value); } - internal PixelFormat[] PixelFormats => Get(() => Enum.GetValues().Where(pf => pf.IsValidFormat()).OrderBy(pf => pf & PixelFormat.Max).ToArray()); + + internal PixelFormat[] PixelFormats => Get(() => Enum.GetValues() + .Where(pf => pf.IsValidFormat()) + // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags + .OrderBy(pf => pf & PixelFormat.Max) + .ToArray()); + internal PixelFormat PixelFormat { get => Get(PixelFormat.Format32bppArgb); set => Set(value); } internal bool PixelFormatEnabled { get => Get(); set => Set(value); } @@ -85,7 +90,7 @@ internal class DebuggerTestFormViewModel : ObservableObjectBase internal bool SingleColor { get => Get(); set => Set(value); } internal bool ImageFromFile { get => Get(); set => Set(value); } - internal string FileName { get => Get(); set => Set(value); } + internal string? FileName { get => Get(); set => Set(value); } internal bool FileAsImage { get => Get(); set => Set(value); } internal bool FileAsBitmap { get => Get(); set => Set(value); } internal bool FileAsMetafile { get => Get(); set => Set(value); } @@ -95,17 +100,17 @@ internal class DebuggerTestFormViewModel : ObservableObjectBase internal bool AsReadOnlyEnabled { get => Get(); set => Set(value); } internal bool CanDebug { get => Get(); set => Set(value); } - internal Image PreviewImage { get => Get(); set => Set(value); } + internal Image? PreviewImage { get => Get(); set => Set(value); } - internal Action ErrorCallback { get => Get>(); set => Set(value); } - internal Func GetHwndCallback { get => Get>(); set => Set(value); } - internal Func GetClipCallback { get => Get>(); set => Set(value); } + internal Action? ErrorCallback { get => Get?>(); set => Set(value); } + internal Func? GetHwndCallback { get => Get?>(); set => Set(value); } + internal Func? GetClipCallback { get => Get?>(); set => Set(value); } internal ICommand DebugCommand => Get(() => new SimpleCommand(OnDebugCommand)); internal ICommand DirectViewCommand => Get(() => new SimpleCommand(OnViewDirectCommand)); - private object TestObject { get => Get(); set => Set(value); } - private Bitmap BitmapDataOwner { get => Get(); set => Set(value); } + private object? TestObject { get => Get(); set => Set(value); } + private Bitmap? BitmapDataOwner { get => Get(); set => Set(value); } #endregion @@ -113,7 +118,7 @@ internal class DebuggerTestFormViewModel : ObservableObjectBase #region Static Methods - private static Image FromPalette(IList palette) + private static Image? FromPalette(IList palette) { var size = palette.Count; if (size == 0) @@ -130,7 +135,7 @@ private static Metafile GenerateMetafile() //Set up reference Graphic Graphics refGraph = Graphics.FromHwnd(IntPtr.Zero); IntPtr hdc = refGraph.GetHdc(); - Metafile result = new Metafile(hdc, new Rectangle(0, 0, 100, 100), MetafileFrameUnit.Pixel, EmfType.EmfOnly, "Test"); + var result = new Metafile(hdc, new Rectangle(0, 0, 100, 100), MetafileFrameUnit.Pixel, EmfType.EmfOnly, "Test"); //Draw some silly drawing using (var g = Graphics.FromImage(result)) @@ -160,9 +165,9 @@ private static Metafile GenerateMetafile() protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) { base.OnPropertyChanged(e); - if (e.NewValue is true && radioGroups.FirstOrDefault(g => g.Contains(e.PropertyName)) is HashSet group) + if (e.NewValue is true && radioGroups.FirstOrDefault(g => g.Contains(e.PropertyName!)) is HashSet group) { - AdjustRadioGroup(e.PropertyName, group); + AdjustRadioGroup(e.PropertyName!, group); if (group.Contains(nameof(Bitmap))) { AsImageEnabled = e.PropertyName.In(nameof(Bitmap), nameof(Metafile), nameof(HIcon), nameof(ManagedIcon)); @@ -184,7 +189,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) if (e.PropertyName == nameof(TestObject)) { - var obj = TestObject; + object? obj = TestObject; PreviewImage = GetPreviewImage(obj); CanDebug = obj != null; } @@ -212,7 +217,7 @@ private void AdjustRadioGroup(string propertyName, IEnumerable group) } } - private object GenerateObject() + private object? GenerateObject() { FreeTestObject(); @@ -220,13 +225,13 @@ private object GenerateObject() { // actually the transient steps should be disposed, too... as this is just a test, now we rely on the destructor if (Bitmap) - return Icons.Shield.ExtractBitmap(0).ConvertPixelFormat(PixelFormat); + return Icons.Shield.ExtractBitmap(0)!.ConvertPixelFormat(PixelFormat); if (Metafile) return GenerateMetafile(); if (HIcon) - return AsImage ? SystemIcons.Application.ToMultiResBitmap() : (object)SystemIcons.Application; + return AsImage ? SystemIcons.Application.ToMultiResBitmap() : SystemIcons.Application; if (ManagedIcon) - return AsImage ? Icons.Application.ToMultiResBitmap() : (object)Icons.Application; + return AsImage ? Icons.Application.ToMultiResBitmap() : Icons.Application; if (GraphicsBitmap) return GetBitmapGraphics(); if (GraphicsHwnd) @@ -241,7 +246,7 @@ private object GenerateObject() if (ImageFromFile) return FromFile(FileName); } - catch (Exception e) when (!(e is StackOverflowException)) + catch (Exception e) when (e is not StackOverflowException) { ErrorCallback?.Invoke($"Could not generate test object: {e.Message}"); return null; @@ -250,7 +255,7 @@ private object GenerateObject() return null; } - private Image GetPreviewImage(object obj) + private Image? GetPreviewImage(object? obj) { try { @@ -268,7 +273,7 @@ static Image ToSupportedFormat(Image image) => case Graphics graphics: return graphics.ToBitmap(false); case BitmapData _: - return ToSupportedFormat((Image)BitmapDataOwner.Clone()); + return ToSupportedFormat((Image)BitmapDataOwner!.Clone()); case ColorPalette palette: return FromPalette(palette.Entries); case Color color: @@ -277,18 +282,14 @@ static Image ToSupportedFormat(Image image) => return null; } } - catch (Exception e) when (!(e is StackOverflowException)) + catch (Exception e) when (e is not StackOverflowException) { ErrorCallback?.Invoke($"Could not generate preview image: {e.Message}"); return null; } } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, the stream is passed to an image so must not be disposed")] - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", - Justification = "This is just a test application")] - private object FromFile(string fileName) + private object? FromFile(string? fileName) { try { @@ -298,14 +299,14 @@ private object FromFile(string fileName) if (FileAsIcon) return Icons.FromStream(stream); var image = Image.FromStream(stream); - if (FileAsBitmap && !(image is Bitmap)) + if (FileAsBitmap && image is not System.Drawing.Bitmap) { image.Dispose(); ErrorCallback?.Invoke("The file is not a Bitmap"); return null; } - if (FileAsMetafile && !(image is Metafile)) + if (FileAsMetafile && image is not System.Drawing.Imaging.Metafile) { image.Dispose(); ErrorCallback?.Invoke("The file is not a Metafile"); @@ -314,20 +315,20 @@ private object FromFile(string fileName) return image; } - catch (Exception e) when (!(e is StackOverflowException)) + catch (Exception e) when (e is not StackOverflowException) { ErrorCallback?.Invoke($"Could not open file: {e.Message}"); return null; } } - private Graphics GetBitmapGraphics() + private Graphics? GetBitmapGraphics() { try { - return Graphics.FromImage(Icons.Shield.ExtractBitmap(0).ConvertPixelFormat(PixelFormat)); + return Graphics.FromImage(Icons.Shield.ExtractBitmap(0)!.ConvertPixelFormat(PixelFormat)); } - catch (Exception e) when (!(e is StackOverflowException)) + catch (Exception e) when (e is not StackOverflowException) { ErrorCallback?.Invoke($"Could not create Graphics from a Bitmap with PixelFormat '{PixelFormat}': {e.Message}"); return null; @@ -346,7 +347,7 @@ private Graphics GetWindowGraphics() private BitmapData GetBitmapData(PixelFormat pixelFormat) { - var bitmap = Icons.Shield.ExtractBitmap(0); + Bitmap bitmap = Icons.Shield.ExtractBitmap(0)!; if (pixelFormat != bitmap.PixelFormat) bitmap = bitmap.ConvertPixelFormat(pixelFormat); BitmapDataOwner = bitmap; @@ -361,7 +362,7 @@ private void FreeTestObject() disposable.Dispose(); break; case BitmapData bitmapData: - var bitmap = BitmapDataOwner; + Bitmap bitmap = BitmapDataOwner!; bitmap.UnlockBits(bitmapData); bitmap.Dispose(); BitmapDataOwner = null; @@ -380,19 +381,19 @@ private void OnViewDirectCommand() case Image image: if (!ImageFromFile && AsImage || ImageFromFile && FileAsImage) { - Image newImage = DebuggerHelper.DebugImage(image, !AsReadOnly, hwnd); + Image? newImage = DebuggerHelper.DebugImage(image, !AsReadOnly, hwnd); if (newImage != image) TestObject = newImage; } else if (image is Metafile metafile) { - Metafile newMetafile = DebuggerHelper.DebugMetafile(metafile, !AsReadOnly, hwnd); + Metafile? newMetafile = DebuggerHelper.DebugMetafile(metafile, !AsReadOnly, hwnd); if (newMetafile != image) TestObject = newMetafile; } else if (image is Bitmap bitmap) { - Bitmap newBitmap = DebuggerHelper.DebugBitmap(bitmap, !AsReadOnly, hwnd); + Bitmap? newBitmap = DebuggerHelper.DebugBitmap(bitmap, !AsReadOnly, hwnd); if (newBitmap != bitmap) TestObject = newBitmap; } @@ -400,7 +401,7 @@ private void OnViewDirectCommand() break; case Icon icon: - Icon newIcon = DebuggerHelper.DebugIcon(icon, !AsReadOnly, hwnd); + Icon? newIcon = DebuggerHelper.DebugIcon(icon, !AsReadOnly, hwnd); if (newIcon != icon) TestObject = newIcon; break; @@ -414,7 +415,7 @@ private void OnViewDirectCommand() break; case ColorPalette palette: - ColorPalette newPalette = DebuggerHelper.DebugPalette(palette, !AsReadOnly, hwnd); + ColorPalette? newPalette = DebuggerHelper.DebugPalette(palette, !AsReadOnly, hwnd); if (newPalette != null) { TestObject = newPalette; @@ -431,10 +432,10 @@ private void OnViewDirectCommand() break; default: - throw new InvalidOperationException($"Unexpected object type: {TestObject.GetType()}"); + throw new InvalidOperationException($"Unexpected object type: {TestObject?.GetType()}"); } } - catch (Exception e) when (!(e is StackOverflowException)) + catch (Exception e) when (e is not StackOverflowException) { ErrorCallback?.Invoke($"Failed to view object: {e.Message}"); } @@ -442,14 +443,14 @@ private void OnViewDirectCommand() private void OnDebugCommand() { - object testObject = TestObject; + object? testObject = TestObject; if (testObject == null) return; Type targetType = testObject is Image && (!ImageFromFile && AsImage || ImageFromFile && FileAsImage) ? typeof(Image) : testObject.GetType(); - DebuggerVisualizerAttribute attr = debuggerVisualizers.GetValueOrDefault(targetType); + DebuggerVisualizerAttribute? attr = debuggerVisualizers.GetValueOrDefault(targetType); if (attr == null) { ErrorCallback?.Invoke($"No debugger visualizer found for type {targetType}"); @@ -460,13 +461,13 @@ private void OnDebugCommand() { var windowService = new TestWindowService(); var objectProvider = new TestObjectProvider(testObject) { IsObjectReplaceable = !AsReadOnly }; - DialogDebuggerVisualizer debugger = (DialogDebuggerVisualizer)Reflector.CreateInstance(Reflector.ResolveType(attr.VisualizerTypeName)); - objectProvider.Serializer = (VisualizerObjectSource)Reflector.CreateInstance(Reflector.ResolveType(attr.VisualizerObjectSourceTypeName)); + DialogDebuggerVisualizer debugger = (DialogDebuggerVisualizer)Reflector.CreateInstance(Reflector.ResolveType(attr.VisualizerTypeName)!); + objectProvider.Serializer = (VisualizerObjectSource)Reflector.CreateInstance(Reflector.ResolveType(attr.VisualizerObjectSourceTypeName!)!); Reflector.InvokeMethod(debugger, "Show", windowService, objectProvider); if (objectProvider.ObjectReplaced) TestObject = objectProvider.Object; } - catch (Exception e) when (!(e is StackOverflowException)) + catch (Exception e) when (e is not StackOverflowException) { ErrorCallback?.Invoke($"Failed to debug object: {e.Message}"); } diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/TestObjectProvider.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/TestObjectProvider.cs index 52e7cb8..d3e1cad 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/TestObjectProvider.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/TestObjectProvider.cs @@ -26,6 +26,16 @@ #endregion +#region Suppressions + +#if NET5_0_OR_GREATER +#pragma warning disable SYSLIB0011 // Type or member is obsolete - must use BinaryFormatter to be compatible with the MS implementation +#pragma warning disable IDE0079 // Remove unnecessary suppression - must use BinaryFormatter to be compatible with the MS implementation +#pragma warning disable CS0618 // Use of obsolete symbol - as above +#endif + +#endregion + namespace KGySoft.Drawing.DebuggerVisualizers.Test.ViewModel { internal class TestObjectProvider : IVisualizerObjectProvider @@ -42,7 +52,7 @@ internal class TestObjectProvider : IVisualizerObjectProvider internal object Object { get; private set; } - internal VisualizerObjectSource Serializer { get; set; } + internal VisualizerObjectSource Serializer { get; set; } = default!; internal bool ObjectReplaced { get; private set; } @@ -60,7 +70,7 @@ internal class TestObjectProvider : IVisualizerObjectProvider public Stream GetData() { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); Serializer.GetData(Object, ms); ms.Position = 0; return ms; diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/_Classes/OSUtils.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/_Classes/OSUtils.cs index 7627c48..d46fd64 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/_Classes/OSUtils.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/_Classes/OSUtils.cs @@ -17,7 +17,6 @@ #region Usings using System; -using System.Drawing; using KGySoft.CoreLibraries; diff --git a/KGySoft.Drawing.DebuggerVisualizers/DebuggerHelper.cs b/KGySoft.Drawing.DebuggerVisualizers/DebuggerHelper.cs index 709bd39..eee31cd 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/DebuggerHelper.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/DebuggerHelper.cs @@ -47,9 +47,9 @@ public static class DebuggerHelper /// If specified, then the created dialog will be owned by the window that has specified handle. This parameter is optional. ///
Default value: IntPtr.Zero. /// An that is returned by the debugger. If is , then this will be always the original . - public static Image DebugImage(Image image, bool isReplaceable = true, IntPtr ownerWindowHandle = default) + public static Image? DebugImage(Image? image, bool isReplaceable = true, IntPtr ownerWindowHandle = default) { - using (IViewModel vm = ViewModelFactory.FromImage(image, !isReplaceable)) + using (IViewModel vm = ViewModelFactory.FromImage(image, !isReplaceable)) { ViewFactory.ShowDialog(vm, ownerWindowHandle); return vm.IsModified ? vm.GetEditedModel() : image; @@ -64,9 +64,9 @@ public static Image DebugImage(Image image, bool isReplaceable = true, IntPtr ow /// If specified, then the created dialog will be owned by the window that has specified handle. This parameter is optional. ///
Default value: IntPtr.Zero. /// A that is returned by the debugger. If is , then this will be always the original . - public static Bitmap DebugBitmap(Bitmap bitmap, bool isReplaceable = true, IntPtr ownerWindowHandle = default) + public static Bitmap? DebugBitmap(Bitmap? bitmap, bool isReplaceable = true, IntPtr ownerWindowHandle = default) { - using (IViewModel vm = ViewModelFactory.FromBitmap(bitmap, !isReplaceable)) + using (IViewModel vm = ViewModelFactory.FromBitmap(bitmap, !isReplaceable)) { ViewFactory.ShowDialog(vm, ownerWindowHandle); return vm.IsModified ? vm.GetEditedModel() : bitmap; @@ -81,9 +81,9 @@ public static Bitmap DebugBitmap(Bitmap bitmap, bool isReplaceable = true, IntPt /// If specified, then the created dialog will be owned by the window that has specified handle. This parameter is optional. ///
Default value: IntPtr.Zero. /// A that is returned by the debugger. If is , then this will be always the original . - public static Metafile DebugMetafile(Metafile metafile, bool isReplaceable = true, IntPtr ownerWindowHandle = default) + public static Metafile? DebugMetafile(Metafile? metafile, bool isReplaceable = true, IntPtr ownerWindowHandle = default) { - using (IViewModel vm = ViewModelFactory.FromMetafile(metafile, !isReplaceable)) + using (IViewModel vm = ViewModelFactory.FromMetafile(metafile, !isReplaceable)) { ViewFactory.ShowDialog(vm, ownerWindowHandle); return vm.IsModified ? vm.GetEditedModel() : metafile; @@ -98,9 +98,9 @@ public static Metafile DebugMetafile(Metafile metafile, bool isReplaceable = tru /// If specified, then the created dialog will be owned by the window that has specified handle. This parameter is optional. ///
Default value: IntPtr.Zero. /// An that is returned by the debugger. If is , then this will be always the original . - public static Icon DebugIcon(Icon icon, bool isReplaceable = true, IntPtr ownerWindowHandle = default) + public static Icon? DebugIcon(Icon? icon, bool isReplaceable = true, IntPtr ownerWindowHandle = default) { - using (IViewModel vm = ViewModelFactory.FromIcon(icon, !isReplaceable)) + using (IViewModel vm = ViewModelFactory.FromIcon(icon, !isReplaceable)) { ViewFactory.ShowDialog(vm, ownerWindowHandle); return vm.IsModified ? vm.GetEditedModel() : icon; @@ -143,7 +143,7 @@ public static void DebugGraphics(Graphics graphics, IntPtr ownerWindowHandle = d /// If specified, then the created dialog will be owned by the window that has specified handle. This parameter is optional. ///
Default value: IntPtr.Zero. /// A non- instance, when the palette has been edited; otherwise, . - public static ColorPalette DebugPalette(ColorPalette palette, bool isReplaceable, IntPtr ownerWindowHandle = default) + public static ColorPalette? DebugPalette(ColorPalette palette, bool isReplaceable, IntPtr ownerWindowHandle = default) { if (palette == null) throw new ArgumentNullException(nameof(palette), PublicResources.ArgumentNull); @@ -186,25 +186,25 @@ public static ColorPalette DebugPalette(ColorPalette palette, bool isReplaceable #region Internal Methods - internal static ImageReference DebugImage(ImageInfo imageInfo, bool isReplaceable) + internal static ImageReference? DebugImage(ImageInfo imageInfo, bool isReplaceable) { using (IViewModel viewModel = ViewModelFactory.FromImage(imageInfo, !isReplaceable)) return DebugImageInfo(viewModel, isReplaceable); } - internal static ImageReference DebugBitmap(ImageInfo bitmapInfo, bool isReplaceable) + internal static ImageReference? DebugBitmap(ImageInfo bitmapInfo, bool isReplaceable) { using (IViewModel viewModel = ViewModelFactory.FromBitmap(bitmapInfo, !isReplaceable)) return DebugImageInfo(viewModel, isReplaceable); } - internal static ImageReference DebugMetafile(ImageInfo metafileInfo, bool isReplaceable) + internal static ImageReference? DebugMetafile(ImageInfo metafileInfo, bool isReplaceable) { using (IViewModel viewModel = ViewModelFactory.FromMetafile(metafileInfo, !isReplaceable)) return DebugImageInfo(viewModel, isReplaceable); } - internal static ImageReference DebugIcon(ImageInfo iconInfo, bool isReplaceable) + internal static ImageReference? DebugIcon(ImageInfo iconInfo, bool isReplaceable) { using (IViewModel viewModel = ViewModelFactory.FromIcon(iconInfo, !isReplaceable)) return DebugImageInfo(viewModel, isReplaceable); @@ -226,7 +226,7 @@ internal static void DebugGraphics(GraphicsInfo graphicsInfo) #region Private Methods - private static ImageReference DebugImageInfo(IViewModel viewModel, bool isReplaceable) + private static ImageReference? DebugImageInfo(IViewModel viewModel, bool isReplaceable) { ViewFactory.ShowDialog(viewModel); if (isReplaceable && viewModel.IsModified) diff --git a/KGySoft.Drawing.DebuggerVisualizers/GlobalSuppressions.cs b/KGySoft.Drawing.DebuggerVisualizers/GlobalSuppressions.cs index 7b6dae2..4e5b17e 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/GlobalSuppressions.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/GlobalSuppressions.cs @@ -7,3 +7,4 @@ [assembly: SuppressMessage("Style", "IDE0063:Use simple 'using' statement", Justification = "Decided individually")] [assembly: SuppressMessage("Style", "IDE0017:Simplify object initialization", Justification = "Decided individually")] +[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "Decided individually")] diff --git a/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj b/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj index 5ed22bc..da50329 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj @@ -1,7 +1,7 @@  - + net45 false KGySoft.Drawing.DebuggerVisualizers @@ -14,6 +14,7 @@ false LICENSE György Kőszeg + enable @@ -23,7 +24,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + NU1701 + diff --git a/KGySoft.Drawing.DebuggerVisualizers/Model/ColorPaletteReference.cs b/KGySoft.Drawing.DebuggerVisualizers/Model/ColorPaletteReference.cs index 5c9d0ca..3719a4d 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Model/ColorPaletteReference.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Model/ColorPaletteReference.cs @@ -17,16 +17,20 @@ #region Usings using System; -using System.Diagnostics.CodeAnalysis; -using System.Drawing; using System.Drawing.Imaging; using System.IO; -using System.Linq; using System.Runtime.Serialization; using System.Security; using KGySoft.Drawing.DebuggerVisualizers.Serialization; -using KGySoft.Drawing.ImagingTools.Model; + +#endregion + +#region Suppressions + +#if NETCOREAPP3_0_OR_GREATER +#pragma warning disable 8766 // false alarm, GetRealObject CAN return null +#endif #endregion @@ -37,13 +41,13 @@ internal sealed class ColorPaletteReference : IObjectReference { #region Fields - private readonly byte[] rawData; + private readonly byte[]? rawData; #endregion #region Constructors - internal ColorPaletteReference(ColorPalette palette) + internal ColorPaletteReference(ColorPalette? palette) { if (palette == null) return; @@ -60,12 +64,12 @@ internal ColorPaletteReference(ColorPalette palette) #region Methods [SecurityCritical] - public object GetRealObject(StreamingContext context) + public object? GetRealObject(StreamingContext context) { if (rawData == null) return null; - using MemoryStream ms = new MemoryStream(rawData); + using var ms = new MemoryStream(rawData); return SerializationHelper.DeserializeColorPalette(ms); } diff --git a/KGySoft.Drawing.DebuggerVisualizers/Model/ColorReference.cs b/KGySoft.Drawing.DebuggerVisualizers/Model/ColorReference.cs index 2b9ca92..3b974ee 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Model/ColorReference.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Model/ColorReference.cs @@ -17,16 +17,12 @@ #region Usings using System; -using System.Diagnostics.CodeAnalysis; using System.Drawing; -using System.Drawing.Imaging; using System.IO; -using System.Linq; using System.Runtime.Serialization; using System.Security; using KGySoft.Drawing.DebuggerVisualizers.Serialization; -using KGySoft.Drawing.ImagingTools.Model; #endregion @@ -59,10 +55,7 @@ internal ColorReference(Color color) [SecurityCritical] public object GetRealObject(StreamingContext context) { - if (rawData == null) - return null; - - using MemoryStream ms = new MemoryStream(rawData); + using var ms = new MemoryStream(rawData); return SerializationHelper.DeserializeColor(ms); } diff --git a/KGySoft.Drawing.DebuggerVisualizers/Model/ImageReference.cs b/KGySoft.Drawing.DebuggerVisualizers/Model/ImageReference.cs index 2bbd8b2..507cd22 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Model/ImageReference.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Model/ImageReference.cs @@ -17,7 +17,6 @@ #region Usings using System; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.IO; using System.Linq; @@ -29,6 +28,14 @@ #endregion +#region Suppressions + +#if NETCOREAPP3_0_OR_GREATER +#pragma warning disable 8766 // false alarm, GetRealObject CAN return null +#endif + +#endregion + namespace KGySoft.Drawing.DebuggerVisualizers.Model { [Serializable] @@ -36,9 +43,9 @@ internal sealed class ImageReference : IObjectReference { #region Fields - private readonly string fileName; + private readonly string? fileName; private readonly bool asIcon; - private readonly byte[] rawData; + private readonly byte[]? rawData; #endregion @@ -69,7 +76,7 @@ private byte[] SerializeImage(ImageInfo imageInfo) { if (asIcon) { - (imageInfo.Icon ?? imageInfo.GetCreateIcon()).SaveAsIcon(ms); + (imageInfo.Icon ?? imageInfo.GetCreateIcon()!).SaveAsIcon(ms); return ms.ToArray(); } @@ -84,9 +91,9 @@ private byte[] SerializeImage(ImageInfo imageInfo) using (var inner = new MemoryStream()) { if (imageInfo.Type == ImageInfoType.Pages) - imageInfo.Frames.Select(f => f.Image).SaveAsMultipageTiff(inner); + imageInfo.Frames!.Select(f => f.Image!).SaveAsMultipageTiff(inner); else - (imageInfo.Icon ?? imageInfo.GetCreateIcon()).SaveAsIcon(inner); + (imageInfo.Icon ?? imageInfo.GetCreateIcon()!).SaveAsIcon(inner); bw.Write(true); // AsImage bw.Write((int)inner.Length); @@ -96,11 +103,11 @@ private byte[] SerializeImage(ImageInfo imageInfo) break; case ImageInfoType.Animation: - SerializationHelper.WriteImage(bw, imageInfo.GetCreateImage()); + SerializationHelper.WriteImage(bw, imageInfo.GetCreateImage()!); break; default: - SerializationHelper.WriteImage(bw, imageInfo.Image); + SerializationHelper.WriteImage(bw, imageInfo.Image!); break; } @@ -114,9 +121,7 @@ private byte[] SerializeImage(ImageInfo imageInfo) #region Instance Methods [SecurityCritical] - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, the icon is disposed by the container ImageInfo")] - public object GetRealObject(StreamingContext context) + public object? GetRealObject(StreamingContext context) { if (fileName == null && rawData == null) return null; @@ -137,12 +142,12 @@ public object GetRealObject(StreamingContext context) // special handling for icon files: as a Bitmap icons may throw an exception using (var info = new ImageInfo(new Icon(fileName))) - return info.GetCreateImage().Clone(); + return info.GetCreateImage()!.Clone(); } } - MemoryStream ms = new MemoryStream(rawData); - return asIcon ? (object)new Icon(ms) : SerializationHelper.ReadImage(new BinaryReader(ms)); + var ms = new MemoryStream(rawData!); + return asIcon ? new Icon(ms) : SerializationHelper.ReadImage(new BinaryReader(ms)); } #endregion diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/BitmapDataSerializationInfo.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/BitmapDataSerializationInfo.cs index bf9a6fa..966352e 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/BitmapDataSerializationInfo.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/BitmapDataSerializationInfo.cs @@ -17,7 +17,6 @@ #region Usings using System; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Imaging; using System.IO; @@ -32,14 +31,12 @@ internal class BitmapDataSerializationInfo : IDisposable { #region Properties - internal BitmapDataInfo BitmapDataInfo { get; private set; } + internal BitmapDataInfo BitmapDataInfo { get; private set; } = default!; #endregion #region Constructors - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, the stream must not be disposed and the leaveOpen parameter is not available on every targeted platform")] internal BitmapDataSerializationInfo(Stream stream) { ReadFrom(new BinaryReader(stream)); @@ -56,7 +53,7 @@ internal BitmapDataSerializationInfo(BitmapData bitmapData) #region Public Methods - public void Dispose() => BitmapDataInfo?.Dispose(); + public void Dispose() => BitmapDataInfo.Dispose(); #endregion @@ -65,10 +62,10 @@ internal BitmapDataSerializationInfo(BitmapData bitmapData) internal void Write(BinaryWriter bw) { // 1. Bitmap - SerializationHelper.WriteImage(bw, BitmapDataInfo.BackingImage); + SerializationHelper.WriteImage(bw, BitmapDataInfo.BackingImage!); // 2. Data - BitmapData data = BitmapDataInfo.BitmapData; + BitmapData data = BitmapDataInfo.BitmapData!; bw.Write(data.Width); bw.Write(data.Height); bw.Write(data.Stride); diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorPaletteSerializationInfo.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorPaletteSerializationInfo.cs index 37caf82..d1d78d9 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorPaletteSerializationInfo.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorPaletteSerializationInfo.cs @@ -16,10 +16,10 @@ #region Usings -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Imaging; using System.IO; + using KGySoft.Reflection; #endregion @@ -30,7 +30,7 @@ internal sealed class ColorPaletteSerializationInfo { #region Properties - internal ColorPalette Palette { get; private set; } + internal ColorPalette Palette { get; private set; } = default!; #endregion @@ -41,8 +41,6 @@ internal ColorPaletteSerializationInfo(ColorPalette palette) Palette = palette; } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, the stream must not be disposed and the leaveOpen parameter is not available on every targeted platform")] internal ColorPaletteSerializationInfo(Stream stream) { ReadFrom(new BinaryReader(stream)); diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorSerializationInfo.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorSerializationInfo.cs index 654c999..8420eb9 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorSerializationInfo.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorSerializationInfo.cs @@ -16,7 +16,6 @@ #region Usings -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.IO; @@ -39,8 +38,6 @@ internal ColorSerializationInfo(Color color) Color = color; } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, the stream must not be disposed and the leaveOpen parameter is not available on every targeted platform")] internal ColorSerializationInfo(Stream stream) { ReadFrom(new BinaryReader(stream)); diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/GraphicsSerializationInfo.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/GraphicsSerializationInfo.cs index 75ea6e6..399696c 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/GraphicsSerializationInfo.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/GraphicsSerializationInfo.cs @@ -17,7 +17,6 @@ #region Usings using System; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; @@ -33,7 +32,7 @@ internal sealed class GraphicsSerializationInfo : IDisposable { #region Properties - internal GraphicsInfo GraphicsInfo { get; private set; } + internal GraphicsInfo GraphicsInfo { get; private set; } = default!; #endregion @@ -44,8 +43,6 @@ internal GraphicsSerializationInfo(Graphics graphics) GraphicsInfo = new GraphicsInfo(graphics); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, the stream must not be disposed and the leaveOpen parameter is not available on every targeted platform")] internal GraphicsSerializationInfo(Stream stream) { ReadFrom(new BinaryReader(stream)); @@ -57,7 +54,7 @@ internal GraphicsSerializationInfo(Stream stream) #region Public Methods - public void Dispose() => GraphicsInfo?.Dispose(); + public void Dispose() => GraphicsInfo.Dispose(); #endregion @@ -66,10 +63,10 @@ internal GraphicsSerializationInfo(Stream stream) internal void Write(BinaryWriter bw) { // 1. Bitmap - SerializationHelper.WriteImage(bw, GraphicsInfo.GraphicsImage); + SerializationHelper.WriteImage(bw, GraphicsInfo.GraphicsImage!); // 2. Transformation matrix - BinarySerializer.SerializeByWriter(bw, GraphicsInfo.Transform.Elements); + BinarySerializer.SerializeByWriter(bw, GraphicsInfo.Transform!.Elements); // 3. Meta bw.Write(GraphicsInfo.OriginalVisibleClipBounds.X); @@ -97,7 +94,7 @@ private void ReadFrom(BinaryReader br) result.GraphicsImage = (Bitmap)SerializationHelper.ReadImage(br); // 2. Transformation matrix - var elements = (float[])BinarySerializer.DeserializeByReader(br); + var elements = (float[])BinarySerializer.DeserializeByReader(br)!; result.Transform = new Matrix(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5]); // 3. Meta diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializationInfo.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializationInfo.cs index c05a0ab..0937f39 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializationInfo.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializationInfo.cs @@ -18,7 +18,6 @@ using System; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Imaging; using System.IO; @@ -34,7 +33,7 @@ internal sealed class ImageSerializationInfo : IDisposable { #region Properties - internal ImageInfo ImageInfo { get; private set; } + internal ImageInfo ImageInfo { get; private set; } = default!; #endregion @@ -42,16 +41,14 @@ internal sealed class ImageSerializationInfo : IDisposable internal ImageSerializationInfo(Image image) { - ImageInfo = new ImageInfo((Image)image?.Clone()); + ImageInfo = new ImageInfo((Image)image.Clone()); } internal ImageSerializationInfo(Icon icon) { - ImageInfo = new ImageInfo((Icon)icon?.Clone()); + ImageInfo = new ImageInfo((Icon)icon.Clone()); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The stream must not be disposed and the leaveOpen parameter is not available in every targeted platform")] internal ImageSerializationInfo(Stream stream) { ReadFrom(new BinaryReader(stream)); @@ -98,7 +95,7 @@ private static void ReadMeta(BinaryReader br, ImageInfoBase imageInfo) #region Public Methods - public void Dispose() => ImageInfo?.Dispose(); + public void Dispose() => ImageInfo.Dispose(); #endregion @@ -120,9 +117,9 @@ internal void Write(BinaryWriter bw) if (saveAsSingleImage) { if (ImageInfo.Type == ImageInfoType.Icon) - SerializationHelper.WriteIcon(bw, ImageInfo.GetCreateIcon()); + SerializationHelper.WriteIcon(bw, ImageInfo.GetCreateIcon()!); else - SerializationHelper.WriteImage(bw, ImageInfo.GetCreateImage()); + SerializationHelper.WriteImage(bw, ImageInfo.GetCreateImage()!); } // Meta is saved even for pages so we will have a general size, etc. @@ -131,11 +128,11 @@ internal void Write(BinaryWriter bw) // 4. Frames (if any) if (ImageInfo.HasFrames) { - bw.Write(ImageInfo.Frames.Length); + bw.Write(ImageInfo.Frames!.Length); foreach (ImageFrameInfo frame in ImageInfo.Frames) { if (!saveAsSingleImage) - SerializationHelper.WriteImage(bw, frame.Image); + SerializationHelper.WriteImage(bw, frame.Image!); WriteMeta(bw, frame); if (ImageInfo.Type == ImageInfoType.Animation) bw.Write(frame.Duration); @@ -169,14 +166,14 @@ private void ReadFrom(BinaryReader br) ReadMeta(br, ImageInfo); - if (imageType == ImageInfoType.SingleImage || imageType == ImageInfoType.Icon && ImageInfo.Icon.GetImagesCount() <= 1) + if (imageType == ImageInfoType.SingleImage || imageType == ImageInfoType.Icon && ImageInfo.Icon!.GetImagesCount() <= 1) return; // 4. Frames (if any) int len = br.ReadInt32(); var frames = new ImageFrameInfo[len]; - Bitmap[] frameImages = savedAsSingleImage - ? imageType == ImageInfoType.Icon ? ImageInfo.Icon.ExtractBitmaps() : ((Bitmap)ImageInfo.Image).ExtractBitmaps() + Bitmap?[] frameImages = savedAsSingleImage + ? imageType == ImageInfoType.Icon ? ImageInfo.Icon!.ExtractBitmaps() : ((Bitmap)ImageInfo.Image!).ExtractBitmaps() : new Bitmap[len]; Debug.Assert(frameImages.Length == frames.Length); for (int i = 0; i < len; i++) diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/SerializationHelper.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/SerializationHelper.cs index f4c473d..d439476 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/SerializationHelper.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/SerializationHelper.cs @@ -17,7 +17,6 @@ #region Usings using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Imaging; using System.IO; @@ -37,56 +36,38 @@ internal static class SerializationHelper { #region Internal Methods - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, the stream must not be disposed and the leaveOpen parameter is not available on every targeted platform")] internal static void SerializeImageInfo(Image image, Stream outgoingData) { using (var imageInfo = new ImageSerializationInfo(image)) imageInfo.Write(new BinaryWriter(outgoingData)); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, the stream must not be disposed and the leaveOpen parameter is not available on every targeted platform")] internal static void SerializeIconInfo(Icon icon, Stream outgoingData) { using (var iconInfo = new ImageSerializationInfo(icon)) iconInfo.Write(new BinaryWriter(outgoingData)); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, the stream must not be disposed and the leaveOpen parameter is not available on every targeted platform")] internal static void SerializeGraphicsInfo(Graphics g, Stream outgoingData) { using (var graphicsInfo = new GraphicsSerializationInfo(g)) graphicsInfo.Write(new BinaryWriter(outgoingData)); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, the stream must not be disposed and the leaveOpen parameter is not available on every targeted platform")] internal static void SerializeBitmapDataInfo(BitmapData bitmapData, Stream outgoingData) { using (var bitmapDataInfo = new BitmapDataSerializationInfo(bitmapData)) bitmapDataInfo.Write(new BinaryWriter(outgoingData)); } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, the stream must not be disposed and the leaveOpen parameter is not available on every targeted platform")] internal static void SerializeColor(Color color, Stream outgoingData) => new ColorSerializationInfo(color).Write(new BinaryWriter(outgoingData)); - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, the stream must not be disposed and the leaveOpen parameter is not available on every targeted platform")] internal static void SerializeColorPalette(ColorPalette palette, Stream outgoingData) => new ColorPaletteSerializationInfo(palette).Write(new BinaryWriter(outgoingData)); - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, disposing would dispose the return value")] internal static ImageInfo DeserializeImageInfo(Stream stream) => new ImageSerializationInfo(stream).ImageInfo; - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, disposing would dispose the return value")] internal static BitmapDataInfo DeserializeBitmapDataInfo(Stream stream) => new BitmapDataSerializationInfo(stream).BitmapDataInfo; - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, disposing would dispose the return value")] internal static GraphicsInfo DeserializeGraphicsInfo(Stream stream) => new GraphicsSerializationInfo(stream).GraphicsInfo; internal static Color DeserializeColor(Stream stream) => new ColorSerializationInfo(stream).Color; @@ -95,7 +76,6 @@ internal static void SerializeBitmapDataInfo(BitmapData bitmapData, Stream outgo internal static void WriteImage(BinaryWriter bw, Image image) { - Debug.Assert(image != null, "Image should not be null here"); int bpp; // writing a decoder compatible stream if image is a metafile... @@ -119,8 +99,7 @@ internal static void WriteImage(BinaryWriter bw, Image image) internal static void WriteIcon(BinaryWriter bw, Icon icon) { - Debug.Assert(icon != null, "Icon should not be null here"); - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { icon.SaveAsIcon(ms); bw.Write((int)ms.Length); @@ -128,8 +107,6 @@ internal static void WriteIcon(BinaryWriter bw, Icon icon) } } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "False alarm, the stream is passed to an image so must not be disposed")] internal static Image ReadImage(BinaryReader br) => br.ReadBoolean() ? Image.FromStream(new MemoryStream(br.ReadBytes(br.ReadInt32()))) : ReadRawBitmap(br); @@ -144,7 +121,7 @@ private static void WriteAsImage(BinaryWriter bw, Image image) { // we must use an inner stream because image.Save (at least TIFF encoder) may overwrite // the stream content before the original start position - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { if (image is Metafile metafile) metafile.Save(ms); @@ -196,7 +173,7 @@ private static Bitmap ReadRawBitmap(BinaryReader br) { var size = new Size(br.ReadInt32(), br.ReadInt32()); var pixelFormat = (PixelFormat)br.ReadInt32(); - Color[] palette = null; + Color[]? palette = null; if (pixelFormat.ToBitsPerPixel() <= 8) { palette = new Color[br.ReadInt32()]; diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/BitmapDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/BitmapDebuggerVisualizer.cs index 727ff65..eaa51dd 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/BitmapDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/BitmapDebuggerVisualizer.cs @@ -43,7 +43,7 @@ protected override void Show(IDialogVisualizerService windowService, IVisualizer { using (ImageInfo imageInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData())) { - ImageReference replacementObject = DebuggerHelper.DebugBitmap(imageInfo, objectProvider.IsObjectReplaceable); + ImageReference? replacementObject = DebuggerHelper.DebugBitmap(imageInfo, objectProvider.IsObjectReplaceable); if (objectProvider.IsObjectReplaceable && replacementObject != null) objectProvider.ReplaceObject(replacementObject); } diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorPaletteDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorPaletteDebuggerVisualizer.cs index a3a1717..0769c57 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorPaletteDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorPaletteDebuggerVisualizer.cs @@ -40,7 +40,7 @@ internal sealed class ColorPaletteDebuggerVisualizer : DialogDebuggerVisualizer /// The object provider. protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) { - ColorPalette newPalette = DebuggerHelper.DebugPalette(SerializationHelper.DeserializeColorPalette(objectProvider.GetData()), objectProvider.IsObjectReplaceable); + ColorPalette? newPalette = DebuggerHelper.DebugPalette(SerializationHelper.DeserializeColorPalette(objectProvider.GetData()), objectProvider.IsObjectReplaceable); if (objectProvider.IsObjectReplaceable && newPalette != null) objectProvider.ReplaceObject(new ColorPaletteReference(newPalette)); } diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/IconDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/IconDebuggerVisualizer.cs index 4768a2b..f5f2b34 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/IconDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/IconDebuggerVisualizer.cs @@ -43,7 +43,7 @@ protected override void Show(IDialogVisualizerService windowService, IVisualizer { using (ImageInfo iconInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData())) { - ImageReference replacementObject = DebuggerHelper.DebugIcon(iconInfo, objectProvider.IsObjectReplaceable); + ImageReference? replacementObject = DebuggerHelper.DebugIcon(iconInfo, objectProvider.IsObjectReplaceable); if (objectProvider.IsObjectReplaceable && replacementObject != null) objectProvider.ReplaceObject(replacementObject); } diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ImageDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ImageDebuggerVisualizer.cs index 057f261..aa6eacb 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ImageDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ImageDebuggerVisualizer.cs @@ -43,7 +43,7 @@ protected override void Show(IDialogVisualizerService windowService, IVisualizer { using (ImageInfo imageInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData())) { - ImageReference replacementObject = DebuggerHelper.DebugImage(imageInfo, objectProvider.IsObjectReplaceable); + ImageReference? replacementObject = DebuggerHelper.DebugImage(imageInfo, objectProvider.IsObjectReplaceable); if (objectProvider.IsObjectReplaceable && replacementObject != null) objectProvider.ReplaceObject(replacementObject); } diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/MetafileDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/MetafileDebuggerVisualizer.cs index 964c3bc..c58a243 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/MetafileDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/MetafileDebuggerVisualizer.cs @@ -43,7 +43,7 @@ protected override void Show(IDialogVisualizerService windowService, IVisualizer { using (ImageInfo imageInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData())) { - ImageReference replacementObject = DebuggerHelper.DebugMetafile(imageInfo, objectProvider.IsObjectReplaceable); + ImageReference? replacementObject = DebuggerHelper.DebugMetafile(imageInfo, objectProvider.IsObjectReplaceable); if (objectProvider.IsObjectReplaceable && replacementObject != null) objectProvider.ReplaceObject(replacementObject); } diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj index 5d226ed..e4d3d83 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj @@ -2,7 +2,7 @@ - net35;net45;net5.0-windows + net45 false KGySoft.Drawing.ImagingTools bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index eee7352..9fb8dda 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -253,7 +253,6 @@ internal static void ApplyResources(object target, string name) } /// Internal Error: {0} - /// Use this method to avoid CA1303 for using string literals in internal errors that never supposed to occur. internal static string InternalError(string msg) => Get("General_InternalErrorFormat", msg); #endregion From 78914b349f26c9092056b5998adae412915bf438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 30 Apr 2021 19:26:38 +0200 Subject: [PATCH 004/211] Debugger visualizers package: enabling nullable references --- .../GlobalSuppressions.cs | 8 ++++++ ...Drawing.DebuggerVisualizers.Package.csproj | 13 ++++----- .../Res.cs | 11 ++++---- .../PackageRegistrationAsyncAttribute.cs | 4 ++- .../ProvideAutoLoadAsyncAttribute.cs | 6 +++-- .../_Classes/DebuggerVisualizersPackage.cs | 27 ++++++++++--------- .../_Classes/ExecuteImagingToolsCommand.cs | 12 +++++---- .../_Classes/Ids.cs | 2 ++ ...eDebuggerVisualizerInstallationsCommand.cs | 19 ++++++------- .../_Classes/ShellDialogs.cs | 2 ++ KGySoft.Drawing.Tools.sln.DotSettings | 1 + 11 files changed, 62 insertions(+), 43 deletions(-) create mode 100644 KGySoft.Drawing.DebuggerVisualizers.Package/GlobalSuppressions.cs diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/GlobalSuppressions.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/GlobalSuppressions.cs new file mode 100644 index 0000000..4a255e4 --- /dev/null +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "Decided individually")] diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj b/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj index 94ea8d8..e0c4bba 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj @@ -1,20 +1,14 @@  + 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) true - - true - - ..\KGySoft.snk - - - Debug AnyCPU 2.0 @@ -24,7 +18,7 @@ Properties KGySoft.Drawing.DebuggerVisualizers.Package KGySoft.Drawing.DebuggerVisualizers.Package - v4.6 + v4.5 true true true @@ -34,6 +28,8 @@ Program $(DevEnvDir)devenv.exe /rootsuffix Exp + latest + enable true @@ -53,6 +49,7 @@ 4 + True True diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/Res.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/Res.cs index 5725348..ed04656 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/Res.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/Res.cs @@ -21,6 +21,8 @@ #endregion +#nullable enable + namespace KGySoft.Drawing.DebuggerVisualizers.Package { #region Usings @@ -72,9 +74,9 @@ internal static class Res private static string Get(string id) => Resources.ResourceManager.GetString(id) ?? String.Format(CultureInfo.InvariantCulture, unavailableResource, id); - private static string Get(string format, params object[] args) => args == null ? format : SafeFormat(format, args); + private static string Get(string format, params object?[]? args) => args == null ? format : SafeFormat(format, args); - private static string SafeFormat(string format, object[] args) + private static string SafeFormat(string format, object?[] args) { try { @@ -83,10 +85,7 @@ private static string SafeFormat(string format, object[] args) { string nullRef = PublicResources.Null; for (; i < args.Length; i++) - { - if (args[i] == null) - args[i] = nullRef; - } + args[i] ??= nullRef; } return String.Format(LanguageSettings.FormattingLanguage, format, args); diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Attributes/PackageRegistrationAsyncAttribute.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Attributes/PackageRegistrationAsyncAttribute.cs index a07d923..49a15b2 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/_Attributes/PackageRegistrationAsyncAttribute.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Attributes/PackageRegistrationAsyncAttribute.cs @@ -24,6 +24,8 @@ #endregion +#nullable enable + namespace KGySoft.Drawing.DebuggerVisualizers.Package { /// @@ -46,7 +48,7 @@ internal sealed class PackageRegistrationAsyncAttribute : RegistrationAttribute public override void Register(RegistrationContext context) { Type t = context.ComponentType; - Key packageKey = null; + Key? packageKey = null; try { packageKey = context.CreateKey(RegKeyName(context)); diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Attributes/ProvideAutoLoadAsyncAttribute.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Attributes/ProvideAutoLoadAsyncAttribute.cs index ac675a5..8e30c24 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/_Attributes/ProvideAutoLoadAsyncAttribute.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Attributes/ProvideAutoLoadAsyncAttribute.cs @@ -22,6 +22,8 @@ #endregion +#nullable enable + namespace KGySoft.Drawing.DebuggerVisualizers.Package { /// @@ -47,8 +49,8 @@ internal sealed class ProvideAutoLoadAsyncAttribute : RegistrationAttribute public override void Register(RegistrationContext context) { - using (Key childKey = context.CreateKey(RegKeyName)) - childKey.SetValue(context.ComponentType.GUID.ToString("B"), backgroundLoad); + using Key childKey = context.CreateKey(RegKeyName); + childKey.SetValue(context.ComponentType.GUID.ToString("B"), backgroundLoad); } public override void Unregister(RegistrationContext context) => context.RemoveValue(RegKeyName, context.ComponentType.GUID.ToString("B")); diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/DebuggerVisualizersPackage.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/DebuggerVisualizersPackage.cs index a6f49b3..24f1890 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/DebuggerVisualizersPackage.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/DebuggerVisualizersPackage.cs @@ -31,6 +31,8 @@ #endregion +#nullable enable + namespace KGySoft.Drawing.DebuggerVisualizers.Package { #region Usings @@ -62,7 +64,7 @@ public sealed class DebuggerVisualizersPackage : Microsoft.VisualStudio.Shell.Pa /// public IVsTask Initialize(IAsyncServiceProvider pServiceProvider, IProfferAsyncService pProfferService, IAsyncProgressCallback pProgressCallback) { - return ThreadHelper.JoinableTaskFactory.RunAsync(async () => + return ThreadHelper.JoinableTaskFactory.RunAsync(async () => { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var shellService = await GetServiceAsync(pServiceProvider, typeof(SVsShell)); @@ -83,8 +85,6 @@ protected override void Initialize() { base.Initialize(); -#pragma warning disable VSTHRD108, VSTHRD010 // Invoke single-threaded types on Main thread: Initialize is on Main thread (see also the assert and IAsyncLoadablePackageInitialize.Initialize) - // returning if async initialization is supported Debug.Assert(ThreadHelper.CheckAccess()); if (GetService(typeof(SAsyncServiceProvider)) is IAsyncServiceProvider) @@ -92,7 +92,6 @@ protected override void Initialize() var uiShellService = GetService(typeof(SVsShell)) as IVsShell; var menuCommandService = GetService(typeof(IMenuCommandService)) as IMenuCommandService; -#pragma warning restore VSTHRD010, VSTHRD010 // Invoke single-threaded types on Main thread DoInitialize(uiShellService, menuCommandService); } @@ -109,9 +108,9 @@ protected override void Dispose(bool disposing) #region Private Methods - private async Task GetServiceAsync(IAsyncServiceProvider asyncServiceProvider, Type serviceType) where T : class + private async Task GetServiceAsync(IAsyncServiceProvider asyncServiceProvider, Type serviceType) where T : class { - T result = null; + T? result = null; await ThreadHelper.JoinableTaskFactory.RunAsync(async () => { Guid serviceTypeGuid = serviceType.GUID; @@ -123,7 +122,7 @@ await ThreadHelper.JoinableTaskFactory.RunAsync(async () => return result; } - private void DoInitialize(IVsShell shellService, IMenuCommandService menuCommandService) + private void DoInitialize(IVsShell? shellService, IMenuCommandService? menuCommandService) { if (initialized) return; @@ -133,7 +132,7 @@ private void DoInitialize(IVsShell shellService, IMenuCommandService menuCommand InitCommands(shellService, menuCommandService); } - private void InstallIfNeeded(IVsShell shellService) + private void InstallIfNeeded(IVsShell? shellService) { if (shellService == null) { @@ -150,19 +149,23 @@ private void InstallIfNeeded(IVsShell shellService) if (installedVersion.Installed && (installedVersion.Version == null || installedVersion.Version >= availableVersion.Version)) return; - InstallationManager.Install(targetPath, out string error, out string warning); + InstallationManager.Install(targetPath, out string? error, out string? warning); if (error != null) ShellDialogs.Error(this, Res.ErrorMessageFailedToInstall(targetPath, error)); else if (warning != null) ShellDialogs.Warning(this, Res.WarningMessageInstallationFinishedWithWarning(targetPath, warning)); else if (installedVersion.Installed && installedVersion.Version != null) - ShellDialogs.Info(this, Res.InfoMessageUpgradeFinished(installedVersion.Version, availableVersion.Version, targetPath)); + ShellDialogs.Info(this, Res.InfoMessageUpgradeFinished(installedVersion.Version, availableVersion.Version!, targetPath)); else - ShellDialogs.Info(this, Res.InfoMessageInstallationFinished(availableVersion.Version, targetPath)); + ShellDialogs.Info(this, Res.InfoMessageInstallationFinished(availableVersion.Version!, targetPath)); } - private void InitCommands(IVsShell shellService, IMenuCommandService menuCommandService) + private void InitCommands(IVsShell? shellService, IMenuCommandService? menuCommandService) { + // no menu point will be added + if (menuCommandService == null) + return; + menuCommandService.AddCommand(ExecuteImagingToolsCommand.GetCreateCommand(this)); menuCommandService.AddCommand(ManageDebuggerVisualizerInstallationsCommand.GetCreateCommand(this, shellService)); } diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ExecuteImagingToolsCommand.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ExecuteImagingToolsCommand.cs index 35e2206..e59378f 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ExecuteImagingToolsCommand.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ExecuteImagingToolsCommand.cs @@ -26,16 +26,18 @@ #endregion +#nullable enable + namespace KGySoft.Drawing.DebuggerVisualizers.Package { internal static class ExecuteImagingToolsCommand { #region Fields - private static MenuCommand commandInstance; - private static IServiceProvider serviceProvider; - private static IViewModel imagingToolsViewModel; - private static IView imagingToolsView; + private static MenuCommand? commandInstance; + private static IServiceProvider? serviceProvider; + private static IViewModel? imagingToolsViewModel; + private static IView? imagingToolsView; #endregion @@ -84,7 +86,7 @@ private static void OnExecuteImagingToolsCommand(object sender, EventArgs e) imagingToolsView?.Dispose(); imagingToolsViewModel?.Dispose(); imagingToolsView = null; - ShellDialogs.Error(serviceProvider, Res.ErrorMessageUnexpectedError(ex.Message)); + ShellDialogs.Error(serviceProvider!, Res.ErrorMessageUnexpectedError(ex.Message)); } } diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/Ids.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/Ids.cs index 6c295fe..751920c 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/Ids.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/Ids.cs @@ -20,6 +20,8 @@ #endregion +#nullable enable + namespace KGySoft.Drawing.DebuggerVisualizers.Package { internal static class Ids diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ManageDebuggerVisualizerInstallationsCommand.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ManageDebuggerVisualizerInstallationsCommand.cs index ed91333..887b916 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ManageDebuggerVisualizerInstallationsCommand.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ManageDebuggerVisualizerInstallationsCommand.cs @@ -27,17 +27,19 @@ #endregion +#nullable enable + namespace KGySoft.Drawing.DebuggerVisualizers.Package { internal static class ManageDebuggerVisualizerInstallationsCommand { #region Fields - private static MenuCommand commandInstance; - private static IServiceProvider serviceProvider; - private static IVsShell shellService; - private static IViewModel manageInstallationsViewModel; - private static IView manageInstallationsView; + private static MenuCommand? commandInstance; + private static IServiceProvider serviceProvider = default!; + private static IVsShell? shellService; + private static IViewModel? manageInstallationsViewModel; + private static IView? manageInstallationsView; #endregion @@ -45,7 +47,7 @@ internal static class ManageDebuggerVisualizerInstallationsCommand #region Internal Methods - internal static MenuCommand GetCreateCommand(IServiceProvider package, IVsShell vsShell) + internal static MenuCommand GetCreateCommand(IServiceProvider package, IVsShell? vsShell) { if (commandInstance == null) { @@ -75,10 +77,9 @@ private static void OnExecuteImagingToolsCommand(object sender, EventArgs e) { if (manageInstallationsView == null || manageInstallationsView.IsDisposed) { + object? documentsDirObj = null; manageInstallationsViewModel?.Dispose(); -#pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread - invoked in UI thread. And ThreadHelper.ThrowIfNotOnUIThread() just emits another warning. - shellService.GetProperty((int)__VSSPROPID2.VSSPROPID_VisualStudioDir, out object documentsDirObj); -#pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread + shellService?.GetProperty((int)__VSSPROPID2.VSSPROPID_VisualStudioDir, out documentsDirObj); manageInstallationsViewModel = ViewModelFactory.CreateManageInstallations(documentsDirObj?.ToString()); manageInstallationsView = ViewFactory.CreateView(manageInstallationsViewModel); } diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ShellDialogs.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ShellDialogs.cs index 751bf12..a281a39 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ShellDialogs.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ShellDialogs.cs @@ -23,6 +23,8 @@ #endregion +#nullable enable + namespace KGySoft.Drawing.DebuggerVisualizers.Package { internal static class ShellDialogs diff --git a/KGySoft.Drawing.Tools.sln.DotSettings b/KGySoft.Drawing.Tools.sln.DotSettings index 7e3d7c9..9e6e84d 100644 --- a/KGySoft.Drawing.Tools.sln.DotSettings +++ b/KGySoft.Drawing.Tools.sln.DotSettings @@ -18,6 +18,7 @@ OS UI VM + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> From 46af6032bd13c063b627c88de928db2425c668cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 30 Apr 2021 20:53:07 +0200 Subject: [PATCH 005/211] Moving all extern methods to WinApi, adjusting security --- .../View/Forms/BaseForm.cs | 23 ++------ .../View/_Extensions/IntPtrExtensions.cs | 28 ++++++++- .../WinApi/Kernel32.cs | 2 + KGySoft.Drawing.ImagingTools/WinApi/User32.cs | 58 +++++++++++++++++++ .../_Classes/InstallationManager.cs | 3 +- .../_Classes/MemoryHelper.cs | 3 +- 6 files changed, 95 insertions(+), 22 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/WinApi/User32.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs index 4245ee5..d6c3274 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs @@ -18,10 +18,12 @@ using System; #if !NET5_0_OR_GREATER +using System.Security; using System.Collections.Specialized; using System.Drawing; using System.Reflection; -using System.Runtime.InteropServices; + +using KGySoft.Drawing.ImagingTools.WinApi; #endif using System.Windows.Forms; @@ -36,22 +38,6 @@ namespace KGySoft.Drawing.ImagingTools.View.Forms /// internal class BaseForm : Form { - #region NativeMethods class - -#if !NET5_0_OR_GREATER - private static class NativeMethods - { - #region Methods - - [DllImport("user32.dll")] - internal static extern bool ScreenToClient(IntPtr hWnd, ref Point lpPoint); - - #endregion - } -#endif - - #endregion - #region Constants #if !NET5_0_OR_GREATER @@ -138,13 +124,14 @@ protected override void Dispose(bool disposing) /// Bugfix: When size grip is visible, and form is above and left of the primary monitor, form cannot be dragged anymore due to forced diagonal resizing. /// Note: Needed only below .NET 5.0 because I fixed this directly in WinForms repository: https://github.com/dotnet/winforms/pull/2032/commits /// + [SecuritySafeCritical] private void WmNCHitTest(ref Message m) { if (FormState[formStateRenderSizeGrip] != 0) { // Here is the bug in original code: LParam contains two shorts. Without the cast negative values are positive ints Point pt = new Point(m.LParam.GetSignedLoWord(), m.LParam.GetSignedHiWord()); - NativeMethods.ScreenToClient(Handle, ref pt); + User32.ScreenToClient(this, ref pt); Size clientSize = ClientSize; if (pt.X >= clientSize.Width - 16 && pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16) { diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/IntPtrExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/IntPtrExtensions.cs index b9604bc..19f65bc 100644 --- a/KGySoft.Drawing.ImagingTools/View/_Extensions/IntPtrExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/IntPtrExtensions.cs @@ -1,10 +1,34 @@ -using System; +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: IntPtrExtensions.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; + +#endregion namespace KGySoft.Drawing.ImagingTools.View { internal static class IntPtrExtensions { + #region Methods + internal static int GetSignedLoWord(this IntPtr p) => (short)(p.ToInt64() & 0xFFFF); internal static int GetSignedHiWord(this IntPtr p) => (short)((p.ToInt64() >> 16) & 0xFFFF); + + #endregion } -} +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/WinApi/Kernel32.cs b/KGySoft.Drawing.ImagingTools/WinApi/Kernel32.cs index 3e15f11..6630d3b 100644 --- a/KGySoft.Drawing.ImagingTools/WinApi/Kernel32.cs +++ b/KGySoft.Drawing.ImagingTools/WinApi/Kernel32.cs @@ -19,11 +19,13 @@ using System; using System.ComponentModel; using System.Runtime.InteropServices; +using System.Security; #endregion namespace KGySoft.Drawing.ImagingTools.WinApi { + [SecurityCritical] internal static class Kernel32 { #region NativeMethods class diff --git a/KGySoft.Drawing.ImagingTools/WinApi/User32.cs b/KGySoft.Drawing.ImagingTools/WinApi/User32.cs new file mode 100644 index 0000000..ae1a556 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/WinApi/User32.cs @@ -0,0 +1,58 @@ +#if !NET5_0_OR_GREATER +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: User32.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Security; +using System.Windows.Forms; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.WinApi +{ + [SecurityCritical] + internal static class User32 + { + #region Nested classes + + #region NativeMethods class + + private static class NativeMethods + { + #region Methods + + [DllImport("user32.dll")] + internal static extern bool ScreenToClient(IntPtr hWnd, ref Point lpPoint); + + #endregion + } + + #endregion + + #endregion + + #region Methods + + internal static void ScreenToClient(Control control, ref Point point) => NativeMethods.ScreenToClient(control.Handle, ref point); + + #endregion + } +} +#endif \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs index 75b60a2..1e34d2c 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs @@ -19,7 +19,7 @@ using System; using System.IO; using System.Linq; - +using System.Security; using KGySoft.CoreLibraries; using KGySoft.Drawing.ImagingTools.Model; #if NET45 @@ -79,6 +79,7 @@ public static class InstallationManager /// The directory where the debugger visualizers have to be installed. /// If the installation fails, then this parameter returns the error message; otherwise, this parameter returns . /// If the installation succeeds with warnings, then this parameter returns the warning message; otherwise, this parameter returns . + [SecuritySafeCritical] public static void Install(string directory, out string? error, out string? warning) { if (directory == null) diff --git a/KGySoft.Drawing.ImagingTools/_Classes/MemoryHelper.cs b/KGySoft.Drawing.ImagingTools/_Classes/MemoryHelper.cs index f83223d..f127d39 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/MemoryHelper.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/MemoryHelper.cs @@ -17,8 +17,8 @@ #region Usings using System; - #if NETFRAMEWORK +using System.Security; using KGySoft.Drawing.ImagingTools.WinApi; #endif @@ -48,6 +48,7 @@ internal static class MemoryHelper private static long MaxMemoryForGC #if NETFRAMEWORK { + [SecuritySafeCritical] get { if (maxMemoryForGC == null) From a4fc86df87f1f41fc16cc42af839cec90f90c262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 30 Apr 2021 22:16:44 +0200 Subject: [PATCH 006/211] Adjusting high DPI handling --- .../System/Runtime.CompilerServices/IsExternalInit.cs | 2 +- .../View/Controls/NotificationLabel.cs | 2 -- .../View/Controls/ScalingToolStrip.cs | 2 +- .../View/Controls/ScalingToolStripDropDownButton.cs | 2 +- KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs | 4 +++- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/IsExternalInit.cs b/KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/IsExternalInit.cs index 7f69684..dc29d34 100644 --- a/KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/IsExternalInit.cs +++ b/KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/IsExternalInit.cs @@ -1,4 +1,4 @@ -#if NETFRAMEWORK +#if NETFRAMEWORK || NETCOREAPP3_0 // ReSharper disable once CheckNamespace namespace System.Runtime.CompilerServices { diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/NotificationLabel.cs b/KGySoft.Drawing.ImagingTools/View/Controls/NotificationLabel.cs index 1c398f7..3dbfd55 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/NotificationLabel.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/NotificationLabel.cs @@ -113,8 +113,6 @@ public override Size GetPreferredSize(Size proposedSize) lastProposedSize = proposedSize; } - //TextFormatFlags formatFlags = this.GetFormatFlags(); - Size padding = GetBordersAndPadding(); Size proposedTextSize = proposedSize - padding; diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs index 81aa0c8..bfcb824 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs @@ -171,6 +171,6 @@ protected override void WndProc(ref Message m) m.Result = (IntPtr)Constants.MA_ACTIVATE; } - #endregion + #endregion } } diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripDropDownButton.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripDropDownButton.cs index 749b59c..2f835f0 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripDropDownButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripDropDownButton.cs @@ -108,6 +108,6 @@ public override Size GetPreferredSize(Size constrainingSize) return preferredSize; } - #endregion + #endregion } } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs index d6c3274..a30206e 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs @@ -71,6 +71,7 @@ internal class BaseForm : Form static BaseForm() { +#if NETFRAMEWORK Type? dpiHelper = Reflector.ResolveType(typeof(Form).Assembly, "System.Windows.Forms.DpiHelper"); if (dpiHelper == null) return; @@ -78,7 +79,8 @@ static BaseForm() // Turning off WinForms auto resize logic to prevent interferences. // Occurs when executed as visualizer debugger and devenv.exe.config contains some random DpiAwareness Reflector.TrySetField(dpiHelper, "isInitialized", true); - Reflector.TrySetField(dpiHelper, "enableHighDpi", false); + Reflector.TrySetField(dpiHelper, "enableHighDpi", false); +#endif } #endregion From 0918c17a78705bb9dc6b162abe7805f33e15e816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 2 May 2021 18:34:58 +0200 Subject: [PATCH 007/211] Using custom serialization when replacing a debugged object --- .../ViewModel/TestObjectProvider.cs | 8 +- .../DebuggerHelper.cs | 12 +-- .../Model/ColorPaletteReference.cs | 78 ------------------- .../Model/ColorReference.cs | 64 --------------- .../Serialization/ColorPaletteSerializer.cs | 5 ++ .../Serialization/ColorSerializer.cs | 5 ++ .../Serialization/IconSerializer.cs | 5 ++ .../Serialization/ImageSerializationInfo.cs | 5 ++ .../Serialization/ImageSerializer.cs | 5 ++ .../Serialization/SerializationHelper.cs | 6 ++ .../BitmapDebuggerVisualizer.cs | 12 ++- .../ColorDebuggerVisualizer.cs | 10 ++- .../ColorPaletteDebuggerVisualizer.cs | 9 ++- .../IconDebuggerVisualizer.cs | 12 ++- .../ImageDebuggerVisualizer.cs | 12 ++- .../MetafileDebuggerVisualizer.cs | 12 ++- .../Model/ImageFrameInfo.cs | 13 ++++ .../Model/ImageInfo.cs | 21 +++++ .../Model/ImageInfoBase.cs | 27 +++++++ KGySoft.Drawing.ImagingTools/Res.cs | 1 + .../ViewModel/ImageVisualizerViewModel.cs | 2 +- 21 files changed, 153 insertions(+), 171 deletions(-) delete mode 100644 KGySoft.Drawing.DebuggerVisualizers/Model/ColorPaletteReference.cs delete mode 100644 KGySoft.Drawing.DebuggerVisualizers/Model/ColorReference.cs diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/TestObjectProvider.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/TestObjectProvider.cs index d3e1cad..d68b29a 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/TestObjectProvider.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/TestObjectProvider.cs @@ -90,7 +90,13 @@ public void ReplaceObject(object newObject) ObjectReplaced = true; } - public void ReplaceData(Stream newObjectData) => throw new NotImplementedException(); + public void ReplaceData(Stream newObjectData) + { + newObjectData.Position = 0L; + Object = Serializer.CreateReplacementObject(Object, newObjectData); + ObjectReplaced = true; + } + public Stream TransferData(Stream outgoingData) => throw new NotImplementedException(); public object TransferObject(object outgoingObject) => throw new NotImplementedException(); diff --git a/KGySoft.Drawing.DebuggerVisualizers/DebuggerHelper.cs b/KGySoft.Drawing.DebuggerVisualizers/DebuggerHelper.cs index eee31cd..a7820e8 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/DebuggerHelper.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/DebuggerHelper.cs @@ -186,25 +186,25 @@ public static void DebugGraphics(Graphics graphics, IntPtr ownerWindowHandle = d #region Internal Methods - internal static ImageReference? DebugImage(ImageInfo imageInfo, bool isReplaceable) + internal static ImageInfo? DebugImage(ImageInfo imageInfo, bool isReplaceable) { using (IViewModel viewModel = ViewModelFactory.FromImage(imageInfo, !isReplaceable)) return DebugImageInfo(viewModel, isReplaceable); } - internal static ImageReference? DebugBitmap(ImageInfo bitmapInfo, bool isReplaceable) + internal static ImageInfo? DebugBitmap(ImageInfo bitmapInfo, bool isReplaceable) { using (IViewModel viewModel = ViewModelFactory.FromBitmap(bitmapInfo, !isReplaceable)) return DebugImageInfo(viewModel, isReplaceable); } - internal static ImageReference? DebugMetafile(ImageInfo metafileInfo, bool isReplaceable) + internal static ImageInfo? DebugMetafile(ImageInfo metafileInfo, bool isReplaceable) { using (IViewModel viewModel = ViewModelFactory.FromMetafile(metafileInfo, !isReplaceable)) return DebugImageInfo(viewModel, isReplaceable); } - internal static ImageReference? DebugIcon(ImageInfo iconInfo, bool isReplaceable) + internal static ImageInfo? DebugIcon(ImageInfo iconInfo, bool isReplaceable) { using (IViewModel viewModel = ViewModelFactory.FromIcon(iconInfo, !isReplaceable)) return DebugImageInfo(viewModel, isReplaceable); @@ -226,11 +226,11 @@ internal static void DebugGraphics(GraphicsInfo graphicsInfo) #region Private Methods - private static ImageReference? DebugImageInfo(IViewModel viewModel, bool isReplaceable) + private static ImageInfo? DebugImageInfo(IViewModel viewModel, bool isReplaceable) { ViewFactory.ShowDialog(viewModel); if (isReplaceable && viewModel.IsModified) - return new ImageReference(viewModel.GetEditedModel()); + return viewModel.GetEditedModel(); return null; } diff --git a/KGySoft.Drawing.DebuggerVisualizers/Model/ColorPaletteReference.cs b/KGySoft.Drawing.DebuggerVisualizers/Model/ColorPaletteReference.cs deleted file mode 100644 index 3719a4d..0000000 --- a/KGySoft.Drawing.DebuggerVisualizers/Model/ColorPaletteReference.cs +++ /dev/null @@ -1,78 +0,0 @@ -#region Copyright - -/////////////////////////////////////////////////////////////////////////////// -// File: ColorPaletteReference.cs -/////////////////////////////////////////////////////////////////////////////// -// Copyright (C) KGy SOFT, 2005-2020 - All Rights Reserved -// -// You should have received a copy of the LICENSE file at the top-level -// directory of this distribution. If not, then this file is considered as -// an illegal copy. -// -// Unauthorized copying of this file, via any medium is strictly prohibited. -/////////////////////////////////////////////////////////////////////////////// - -#endregion - -#region Usings - -using System; -using System.Drawing.Imaging; -using System.IO; -using System.Runtime.Serialization; -using System.Security; - -using KGySoft.Drawing.DebuggerVisualizers.Serialization; - -#endregion - -#region Suppressions - -#if NETCOREAPP3_0_OR_GREATER -#pragma warning disable 8766 // false alarm, GetRealObject CAN return null -#endif - -#endregion - -namespace KGySoft.Drawing.DebuggerVisualizers.Model -{ - [Serializable] - internal sealed class ColorPaletteReference : IObjectReference - { - #region Fields - - private readonly byte[]? rawData; - - #endregion - - #region Constructors - - internal ColorPaletteReference(ColorPalette? palette) - { - if (palette == null) - return; - - using (var ms = new MemoryStream()) - { - SerializationHelper.SerializeColorPalette(palette, ms); - rawData = ms.ToArray(); - } - } - - #endregion - - #region Methods - - [SecurityCritical] - public object? GetRealObject(StreamingContext context) - { - if (rawData == null) - return null; - - using var ms = new MemoryStream(rawData); - return SerializationHelper.DeserializeColorPalette(ms); - } - - #endregion - } -} diff --git a/KGySoft.Drawing.DebuggerVisualizers/Model/ColorReference.cs b/KGySoft.Drawing.DebuggerVisualizers/Model/ColorReference.cs deleted file mode 100644 index 3b974ee..0000000 --- a/KGySoft.Drawing.DebuggerVisualizers/Model/ColorReference.cs +++ /dev/null @@ -1,64 +0,0 @@ -#region Copyright - -/////////////////////////////////////////////////////////////////////////////// -// File: ColorReference.cs -/////////////////////////////////////////////////////////////////////////////// -// Copyright (C) KGy SOFT, 2005-2020 - All Rights Reserved -// -// You should have received a copy of the LICENSE file at the top-level -// directory of this distribution. If not, then this file is considered as -// an illegal copy. -// -// Unauthorized copying of this file, via any medium is strictly prohibited. -/////////////////////////////////////////////////////////////////////////////// - -#endregion - -#region Usings - -using System; -using System.Drawing; -using System.IO; -using System.Runtime.Serialization; -using System.Security; - -using KGySoft.Drawing.DebuggerVisualizers.Serialization; - -#endregion - -namespace KGySoft.Drawing.DebuggerVisualizers.Model -{ - [Serializable] - internal sealed class ColorReference : IObjectReference - { - #region Fields - - private readonly byte[] rawData; - - #endregion - - #region Constructors - - internal ColorReference(Color color) - { - using (var ms = new MemoryStream()) - { - SerializationHelper.SerializeColor(color, ms); - rawData = ms.ToArray(); - } - } - - #endregion - - #region Methods - - [SecurityCritical] - public object GetRealObject(StreamingContext context) - { - using var ms = new MemoryStream(rawData); - return SerializationHelper.DeserializeColor(ms); - } - - #endregion - } -} diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorPaletteSerializer.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorPaletteSerializer.cs index 8c214b3..45c125d 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorPaletteSerializer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorPaletteSerializer.cs @@ -37,6 +37,11 @@ internal class ColorPaletteSerializer : VisualizerObjectSource /// public override void GetData(object target, Stream outgoingData) => SerializationHelper.SerializeColorPalette((ColorPalette)target, outgoingData); + /// + /// Called when the debugged object has been replaced + /// + public override object CreateReplacementObject(object target, Stream incomingData) => SerializationHelper.DeserializeColorPalette(incomingData); + #endregion } } \ No newline at end of file diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorSerializer.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorSerializer.cs index 9a3137b..cfc6867 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorSerializer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ColorSerializer.cs @@ -37,6 +37,11 @@ internal class ColorSerializer : VisualizerObjectSource /// public override void GetData(object target, Stream outgoingData) => SerializationHelper.SerializeColor((Color)target, outgoingData); + /// + /// Called when the debugged object has been replaced + /// + public override object CreateReplacementObject(object target, Stream incomingData) => SerializationHelper.DeserializeColor(incomingData); + #endregion } } \ No newline at end of file diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/IconSerializer.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/IconSerializer.cs index e4a4131..d48270a 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/IconSerializer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/IconSerializer.cs @@ -37,6 +37,11 @@ internal class IconSerializer : VisualizerObjectSource /// public override void GetData(object target, Stream outgoingData) => SerializationHelper.SerializeIconInfo((Icon)target, outgoingData); + /// + /// Called when the debugged object has been replaced + /// + public override object? CreateReplacementObject(object target, Stream incomingData) => SerializationHelper.DeserializeImageInfo(incomingData).GetCreateIcon(); + #endregion } } diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializationInfo.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializationInfo.cs index 0937f39..d6379b0 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializationInfo.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializationInfo.cs @@ -39,6 +39,11 @@ internal sealed class ImageSerializationInfo : IDisposable #region Constructors + internal ImageSerializationInfo(ImageInfo imageInfo) + { + ImageInfo = imageInfo; + } + internal ImageSerializationInfo(Image image) { ImageInfo = new ImageInfo((Image)image.Clone()); diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializer.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializer.cs index c266ba2..32beb9f 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializer.cs @@ -37,6 +37,11 @@ internal class ImageSerializer : VisualizerObjectSource /// public override void GetData(object target, Stream outgoingData) => SerializationHelper.SerializeImageInfo((Image)target, outgoingData); + /// + /// Called when the debugged object has been replaced + /// + public override object? CreateReplacementObject(object target, Stream incomingData) => SerializationHelper.DeserializeImageInfo(incomingData).GetCreateImage(); + #endregion } } diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/SerializationHelper.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/SerializationHelper.cs index d439476..fc2ddd6 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/SerializationHelper.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/SerializationHelper.cs @@ -36,6 +36,12 @@ internal static class SerializationHelper { #region Internal Methods + internal static void SerializeImageInfo(ImageInfo image, Stream outgoingData) + { + using (var imageInfo = new ImageSerializationInfo(image)) + imageInfo.Write(new BinaryWriter(outgoingData)); + } + internal static void SerializeImageInfo(Image image, Stream outgoingData) { using (var imageInfo = new ImageSerializationInfo(image)) diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/BitmapDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/BitmapDebuggerVisualizer.cs index eaa51dd..7aeefad 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/BitmapDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/BitmapDebuggerVisualizer.cs @@ -17,8 +17,8 @@ #region Usings using System.Diagnostics.CodeAnalysis; +using System.IO; -using KGySoft.Drawing.DebuggerVisualizers.Model; using KGySoft.Drawing.DebuggerVisualizers.Serialization; using KGySoft.Drawing.ImagingTools.Model; @@ -43,9 +43,13 @@ protected override void Show(IDialogVisualizerService windowService, IVisualizer { using (ImageInfo imageInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData())) { - ImageReference? replacementObject = DebuggerHelper.DebugBitmap(imageInfo, objectProvider.IsObjectReplaceable); - if (objectProvider.IsObjectReplaceable && replacementObject != null) - objectProvider.ReplaceObject(replacementObject); + ImageInfo? replacementObject = DebuggerHelper.DebugBitmap(imageInfo, objectProvider.IsObjectReplaceable); + if (!objectProvider.IsObjectReplaceable || replacementObject == null) + return; + + using var ms = new MemoryStream(); + SerializationHelper.SerializeImageInfo(replacementObject, ms); + objectProvider.ReplaceData(ms); } } diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorDebuggerVisualizer.cs index 72b8089..43d6df2 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorDebuggerVisualizer.cs @@ -18,8 +18,8 @@ using System.Diagnostics.CodeAnalysis; using System.Drawing; +using System.IO; -using KGySoft.Drawing.DebuggerVisualizers.Model; using KGySoft.Drawing.DebuggerVisualizers.Serialization; using Microsoft.VisualStudio.DebuggerVisualizers; @@ -41,8 +41,12 @@ internal class ColorDebuggerVisualizer : DialogDebuggerVisualizer protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) { Color? newColor = DebuggerHelper.DebugColor(SerializationHelper.DeserializeColor(objectProvider.GetData()), objectProvider.IsObjectReplaceable); - if (objectProvider.IsObjectReplaceable && newColor != null) - objectProvider.ReplaceObject(new ColorReference(newColor.Value)); + if (!objectProvider.IsObjectReplaceable || newColor == null) + return; + + using var ms = new MemoryStream(); + SerializationHelper.SerializeColor(newColor.Value, ms); + objectProvider.ReplaceData(ms); } #endregion diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorPaletteDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorPaletteDebuggerVisualizer.cs index 0769c57..eb188ff 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorPaletteDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorPaletteDebuggerVisualizer.cs @@ -18,6 +18,7 @@ using System.Diagnostics.CodeAnalysis; using System.Drawing.Imaging; +using System.IO; using KGySoft.Drawing.DebuggerVisualizers.Model; using KGySoft.Drawing.DebuggerVisualizers.Serialization; @@ -41,8 +42,12 @@ internal sealed class ColorPaletteDebuggerVisualizer : DialogDebuggerVisualizer protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) { ColorPalette? newPalette = DebuggerHelper.DebugPalette(SerializationHelper.DeserializeColorPalette(objectProvider.GetData()), objectProvider.IsObjectReplaceable); - if (objectProvider.IsObjectReplaceable && newPalette != null) - objectProvider.ReplaceObject(new ColorPaletteReference(newPalette)); + if (!objectProvider.IsObjectReplaceable || newPalette == null) + return; + + using var ms = new MemoryStream(); + SerializationHelper.SerializeColorPalette(newPalette, ms); + objectProvider.ReplaceData(ms); } #endregion diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/IconDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/IconDebuggerVisualizer.cs index f5f2b34..a60cb45 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/IconDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/IconDebuggerVisualizer.cs @@ -17,8 +17,8 @@ #region Usings using System.Diagnostics.CodeAnalysis; +using System.IO; -using KGySoft.Drawing.DebuggerVisualizers.Model; using KGySoft.Drawing.DebuggerVisualizers.Serialization; using KGySoft.Drawing.ImagingTools.Model; @@ -43,9 +43,13 @@ protected override void Show(IDialogVisualizerService windowService, IVisualizer { using (ImageInfo iconInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData())) { - ImageReference? replacementObject = DebuggerHelper.DebugIcon(iconInfo, objectProvider.IsObjectReplaceable); - if (objectProvider.IsObjectReplaceable && replacementObject != null) - objectProvider.ReplaceObject(replacementObject); + ImageInfo? replacementObject = DebuggerHelper.DebugIcon(iconInfo, objectProvider.IsObjectReplaceable); + if (!objectProvider.IsObjectReplaceable || replacementObject == null) + return; + + using var ms = new MemoryStream(); + SerializationHelper.SerializeImageInfo(replacementObject, ms); + objectProvider.ReplaceData(ms); } } diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ImageDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ImageDebuggerVisualizer.cs index aa6eacb..cb6f0e4 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ImageDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ImageDebuggerVisualizer.cs @@ -17,8 +17,8 @@ #region Usings using System.Diagnostics.CodeAnalysis; +using System.IO; -using KGySoft.Drawing.DebuggerVisualizers.Model; using KGySoft.Drawing.DebuggerVisualizers.Serialization; using KGySoft.Drawing.ImagingTools.Model; @@ -43,9 +43,13 @@ protected override void Show(IDialogVisualizerService windowService, IVisualizer { using (ImageInfo imageInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData())) { - ImageReference? replacementObject = DebuggerHelper.DebugImage(imageInfo, objectProvider.IsObjectReplaceable); - if (objectProvider.IsObjectReplaceable && replacementObject != null) - objectProvider.ReplaceObject(replacementObject); + ImageInfo? replacementObject = DebuggerHelper.DebugImage(imageInfo, objectProvider.IsObjectReplaceable); + if (!objectProvider.IsObjectReplaceable || replacementObject == null) + return; + + using var ms = new MemoryStream(); + SerializationHelper.SerializeImageInfo(replacementObject, ms); + objectProvider.ReplaceData(ms); } } diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/MetafileDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/MetafileDebuggerVisualizer.cs index c58a243..01380ca 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/MetafileDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/MetafileDebuggerVisualizer.cs @@ -17,8 +17,8 @@ #region Usings using System.Diagnostics.CodeAnalysis; +using System.IO; -using KGySoft.Drawing.DebuggerVisualizers.Model; using KGySoft.Drawing.DebuggerVisualizers.Serialization; using KGySoft.Drawing.ImagingTools.Model; @@ -43,9 +43,13 @@ protected override void Show(IDialogVisualizerService windowService, IVisualizer { using (ImageInfo imageInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData())) { - ImageReference? replacementObject = DebuggerHelper.DebugMetafile(imageInfo, objectProvider.IsObjectReplaceable); - if (objectProvider.IsObjectReplaceable && replacementObject != null) - objectProvider.ReplaceObject(replacementObject); + ImageInfo? replacementObject = DebuggerHelper.DebugMetafile(imageInfo, objectProvider.IsObjectReplaceable); + if (!objectProvider.IsObjectReplaceable || replacementObject == null) + return; + + using var ms = new MemoryStream(); + SerializationHelper.SerializeImageInfo(replacementObject, ms); + objectProvider.ReplaceData(ms); } } diff --git a/KGySoft.Drawing.ImagingTools/Model/ImageFrameInfo.cs b/KGySoft.Drawing.ImagingTools/Model/ImageFrameInfo.cs index 5f03775..869877f 100644 --- a/KGySoft.Drawing.ImagingTools/Model/ImageFrameInfo.cs +++ b/KGySoft.Drawing.ImagingTools/Model/ImageFrameInfo.cs @@ -41,6 +41,8 @@ public sealed class ImageFrameInfo : ImageInfoBase #region Constructors + #region Public Constructors + /// /// Initializes a new instance of the class from a . /// @@ -53,6 +55,17 @@ public ImageFrameInfo(Bitmap? bitmap) #endregion + #region Internal Constructors + + internal ImageFrameInfo(ImageFrameInfo other) : base(other) + { + Duration = other.Duration; + } + + #endregion + + #endregion + #region Methods /// diff --git a/KGySoft.Drawing.ImagingTools/Model/ImageInfo.cs b/KGySoft.Drawing.ImagingTools/Model/ImageInfo.cs index 1ed512b..b2afbe1 100644 --- a/KGySoft.Drawing.ImagingTools/Model/ImageInfo.cs +++ b/KGySoft.Drawing.ImagingTools/Model/ImageInfo.cs @@ -80,6 +80,8 @@ public sealed class ImageInfo : ImageInfoBase #region Constructors + #region Public Constructors + /// /// Initializes an empty instance of the class. /// The properties are expected to be initialized individually. Use the @@ -115,6 +117,25 @@ public ImageInfo(Icon? icon) #endregion + #region Internal Constructors + + internal ImageInfo(ImageInfo other) : base(other) + { + Type = other.Type; + if (Type == ImageInfoType.None) + return; + + if (other.Icon is Icon icon) + Icon = (Icon)icon.Clone(); + if (other.Frames is ImageFrameInfo[] frames) + Frames = frames.Select(f => new ImageFrameInfo(f)).ToArray(); + FileName = other.FileName; + } + + #endregion + + #endregion + #region Methods #region Public Methods diff --git a/KGySoft.Drawing.ImagingTools/Model/ImageInfoBase.cs b/KGySoft.Drawing.ImagingTools/Model/ImageInfoBase.cs index e2296cc..d356f59 100644 --- a/KGySoft.Drawing.ImagingTools/Model/ImageInfoBase.cs +++ b/KGySoft.Drawing.ImagingTools/Model/ImageInfoBase.cs @@ -92,6 +92,31 @@ public abstract class ImageInfoBase : ValidatingObjectBase #endregion + #region Construction and Destruction + + #region Constructors + + private protected ImageInfoBase() + { + } + + private protected ImageInfoBase(ImageInfoBase other) + { + if (other == null) + throw new ArgumentNullException(nameof(other), PublicResources.ArgumentNull); + + if (other.Image is Image image) + Image = (Image)image.Clone(); + if (other.Palette.Length > 0) + Palette = (Color[])Palette.Clone(); + HorizontalRes = other.HorizontalRes; + VerticalRes = other.VerticalRes; + PixelFormat = other.PixelFormat; + RawFormat = other.RawFormat; + } + + #endregion + #region Destructor @@ -100,6 +125,8 @@ public abstract class ImageInfoBase : ValidatingObjectBase #endregion + #endregion + #region Methods #region Protected Methods diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 9fb8dda..47c8deb 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -50,6 +50,7 @@ internal static class Res UseLanguageSettings = true, }; + // Note: No need to use ThreadSafeCacheFactory here because used only from the UI thread // ReSharper disable once CollectionNeverUpdated.Local private static readonly Cache localizablePropertiesCache = new Cache(GetLocalizableProperties); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs index 8dcaf34..ff5952b 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs @@ -1019,7 +1019,7 @@ private void RotateBitmap(RotateFlipType direction) Icon? IViewModel.GetEditedModel() => Icon?.Clone() as Icon; Bitmap? IViewModel.GetEditedModel() => Image?.Clone() as Bitmap; Metafile? IViewModel.GetEditedModel() => Image?.Clone() as Metafile; - ImageInfo IViewModel.GetEditedModel() => imageTypes == AllowedImageTypes.Icon ? imageInfo.AsIcon() : imageInfo.AsImage(); + ImageInfo IViewModel.GetEditedModel() => new ImageInfo(imageInfo); #endregion From a34a71430023b5ce3f89f5ec96092a1240aa6f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 9 May 2021 12:59:16 +0200 Subject: [PATCH 008/211] Improving performance by avoiding unnecessary cloning, custom serialization of replacement images --- .../DebuggerHelper.cs | 1 - .../Model/ImageReference.cs | 157 ---------------- .../Serialization/IconSerializer.cs | 2 +- .../ImageReplacementSerializationInfo.cs | 177 ++++++++++++++++++ .../Serialization/ImageSerializationInfo.cs | 19 +- .../Serialization/ImageSerializer.cs | 2 +- .../Serialization/SerializationHelper.cs | 28 +-- .../BitmapDebuggerVisualizer.cs | 18 +- .../ColorPaletteDebuggerVisualizer.cs | 1 - .../IconDebuggerVisualizer.cs | 18 +- .../ImageDebuggerVisualizer.cs | 18 +- .../MetafileDebuggerVisualizer.cs | 18 +- .../Model/ImageInfo.cs | 27 +-- .../ViewModel/ImageVisualizerViewModel.cs | 9 +- changelog.txt | 16 ++ 15 files changed, 264 insertions(+), 247 deletions(-) delete mode 100644 KGySoft.Drawing.DebuggerVisualizers/Model/ImageReference.cs create mode 100644 KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageReplacementSerializationInfo.cs diff --git a/KGySoft.Drawing.DebuggerVisualizers/DebuggerHelper.cs b/KGySoft.Drawing.DebuggerVisualizers/DebuggerHelper.cs index a7820e8..01450d3 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/DebuggerHelper.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/DebuggerHelper.cs @@ -21,7 +21,6 @@ using System.Drawing; using System.Drawing.Imaging; -using KGySoft.Drawing.DebuggerVisualizers.Model; using KGySoft.Drawing.ImagingTools.Model; using KGySoft.Drawing.ImagingTools.View; using KGySoft.Drawing.ImagingTools.ViewModel; diff --git a/KGySoft.Drawing.DebuggerVisualizers/Model/ImageReference.cs b/KGySoft.Drawing.DebuggerVisualizers/Model/ImageReference.cs deleted file mode 100644 index 507cd22..0000000 --- a/KGySoft.Drawing.DebuggerVisualizers/Model/ImageReference.cs +++ /dev/null @@ -1,157 +0,0 @@ -#region Copyright - -/////////////////////////////////////////////////////////////////////////////// -// File: ImageReference.cs -/////////////////////////////////////////////////////////////////////////////// -// Copyright (C) KGy SOFT, 2005-2019 - All Rights Reserved -// -// You should have received a copy of the LICENSE file at the top-level -// directory of this distribution. If not, then this file is considered as -// an illegal copy. -// -// Unauthorized copying of this file, via any medium is strictly prohibited. -/////////////////////////////////////////////////////////////////////////////// - -#endregion - -#region Usings - -using System; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Runtime.Serialization; -using System.Security; - -using KGySoft.Drawing.DebuggerVisualizers.Serialization; -using KGySoft.Drawing.ImagingTools.Model; - -#endregion - -#region Suppressions - -#if NETCOREAPP3_0_OR_GREATER -#pragma warning disable 8766 // false alarm, GetRealObject CAN return null -#endif - -#endregion - -namespace KGySoft.Drawing.DebuggerVisualizers.Model -{ - [Serializable] - internal sealed class ImageReference : IObjectReference - { - #region Fields - - private readonly string? fileName; - private readonly bool asIcon; - private readonly byte[]? rawData; - - #endregion - - #region Constructors - - internal ImageReference(ImageInfo imageInfo) - { - if (imageInfo.Type == ImageInfoType.None) - return; - - asIcon = imageInfo.Type == ImageInfoType.Icon; - fileName = imageInfo.FileName; - if (fileName != null) - return; - - rawData = SerializeImage(imageInfo); - } - - #endregion - - #region Methods - - #region Static Methods - - private byte[] SerializeImage(ImageInfo imageInfo) - { - using (var ms = new MemoryStream()) - { - if (asIcon) - { - (imageInfo.Icon ?? imageInfo.GetCreateIcon()!).SaveAsIcon(ms); - return ms.ToArray(); - } - - using (var bw = new BinaryWriter(ms)) - { - switch (imageInfo.Type) - { - case ImageInfoType.Pages: - case ImageInfoType.MultiRes: - // we must use an inner stream because image.Save (at least TIFF encoder) may overwrite - // the stream content before the original start position - using (var inner = new MemoryStream()) - { - if (imageInfo.Type == ImageInfoType.Pages) - imageInfo.Frames!.Select(f => f.Image!).SaveAsMultipageTiff(inner); - else - (imageInfo.Icon ?? imageInfo.GetCreateIcon()!).SaveAsIcon(inner); - - bw.Write(true); // AsImage - bw.Write((int)inner.Length); - inner.WriteTo(ms); - } - - break; - - case ImageInfoType.Animation: - SerializationHelper.WriteImage(bw, imageInfo.GetCreateImage()!); - break; - - default: - SerializationHelper.WriteImage(bw, imageInfo.Image!); - break; - } - - return ms.ToArray(); - } - } - } - - #endregion - - #region Instance Methods - - [SecurityCritical] - public object? GetRealObject(StreamingContext context) - { - if (fileName == null && rawData == null) - return null; - - if (fileName != null) - { - if (asIcon) - return new Icon(fileName); - - try - { - return Image.FromFile(fileName); - } - catch (Exception) - { - if (!fileName.EndsWith(".ico", StringComparison.OrdinalIgnoreCase)) - throw; - - // special handling for icon files: as a Bitmap icons may throw an exception - using (var info = new ImageInfo(new Icon(fileName))) - return info.GetCreateImage()!.Clone(); - } - } - - var ms = new MemoryStream(rawData!); - return asIcon ? new Icon(ms) : SerializationHelper.ReadImage(new BinaryReader(ms)); - } - - #endregion - - #endregion - } -} diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/IconSerializer.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/IconSerializer.cs index d48270a..70435a7 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/IconSerializer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/IconSerializer.cs @@ -40,7 +40,7 @@ internal class IconSerializer : VisualizerObjectSource /// /// Called when the debugged object has been replaced /// - public override object? CreateReplacementObject(object target, Stream incomingData) => SerializationHelper.DeserializeImageInfo(incomingData).GetCreateIcon(); + public override object? CreateReplacementObject(object target, Stream incomingData) => SerializationHelper.DeserializeReplacementIcon(incomingData); #endregion } diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageReplacementSerializationInfo.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageReplacementSerializationInfo.cs new file mode 100644 index 0000000..e1b5aa9 --- /dev/null +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageReplacementSerializationInfo.cs @@ -0,0 +1,177 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: ImageReplacementSerializationInfo.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; + +using KGySoft.Drawing.ImagingTools.Model; + +#endregion + +namespace KGySoft.Drawing.DebuggerVisualizers.Serialization +{ + internal sealed class ImageReplacementSerializationInfo : IDisposable + { + #region Fields + + private readonly ImageInfo? imageInfo; + private readonly Stream? stream; + + #endregion + + #region Constructors + + internal ImageReplacementSerializationInfo(ImageInfo imageInfo) => this.imageInfo = imageInfo; + + internal ImageReplacementSerializationInfo(Stream stream) => this.stream = stream; + + #endregion + + #region Methods + + #region Public Methods + + public void Dispose() + { + imageInfo?.Dispose(); + stream?.Dispose(); + } + + #endregion + + #region Internal Methods + + internal void Write(BinaryWriter bw) + { + // 1.) Image type + bw.Write((byte)imageInfo!.Type); + if (imageInfo.Type == ImageInfoType.None) + return; + + // 2.) File reference + string? fileName = imageInfo.FileName; + bw.Write(fileName != null); + if (fileName != null) + { + bw.Write(fileName); + return; + } + + // 3.) Image content + switch (imageInfo.Type) + { + case ImageInfoType.Icon: + case ImageInfoType.MultiRes: + SerializationHelper.WriteIcon(bw, imageInfo.GetCreateIcon()!); + return; + + case ImageInfoType.Pages: + // we must use an inner stream because image.Save (at least TIFF encoder) may overwrite + // the stream content before the original start position + using (var inner = new MemoryStream()) + { + imageInfo.Frames!.Select(f => f.Image!).SaveAsMultipageTiff(inner); + bw.Write((int)inner.Length); + bw.Flush(); + inner.WriteTo(bw.BaseStream); + } + + return; + + case ImageInfoType.Animation: + // Using GetCreateImage so in case of a changed animated GIF the possible exception is thrown + SerializationHelper.WriteImage(bw, imageInfo.GetCreateImage()!); + return; + + default: + SerializationHelper.WriteImage(bw, imageInfo.Image!); + return; + } + } + + internal object? GetReplacementObject() + { + var br = new BinaryReader(stream!); + + // 1.) Image type + ImageInfoType type = (ImageInfoType)br.ReadByte(); + if (type == ImageInfoType.None) + return null; + + // 2.) From file + string? fileName = br.ReadBoolean() ? br.ReadString() : null; + if (fileName != null) + { + if (type == ImageInfoType.Icon) + { + using FileStream fs = File.OpenRead(fileName); + return Icons.FromStream(fs); + } + + try + { + return Image.FromFile(fileName); + } + catch (Exception) + { + if (!fileName.EndsWith(".ico", StringComparison.OrdinalIgnoreCase)) + throw; + + // special handling for icon files: as a Bitmap icons may throw an exception + using FileStream fs = File.OpenRead(fileName); + using Icon? icon = Icons.FromStream(fs); + return icon?.ExtractNearestBitmap(new Size(UInt16.MaxValue, UInt16.MaxValue), PixelFormat.Format32bppArgb); + } + } + + // 3.) From stream + switch (type) + { + case ImageInfoType.Icon: + return SerializationHelper.ReadIcon(br); + + case ImageInfoType.MultiRes: + using (Icon? icon = SerializationHelper.ReadIcon(br)) + { + try + { + return icon?.ToMultiResBitmap(); + } + catch (Exception) + { + // special handling for icon files: as a Bitmap icons may throw an exception + return icon?.ExtractNearestBitmap(new Size(UInt16.MaxValue, UInt16.MaxValue), PixelFormat.Format32bppArgb); + } + } + + case ImageInfoType.Pages: + return Image.FromStream(new MemoryStream(br.ReadBytes(br.ReadInt32()))); + + default: + return SerializationHelper.ReadImage(br); + } + } + + #endregion + + #endregion + } +} diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializationInfo.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializationInfo.cs index d6379b0..5237faf 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializationInfo.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializationInfo.cs @@ -39,19 +39,14 @@ internal sealed class ImageSerializationInfo : IDisposable #region Constructors - internal ImageSerializationInfo(ImageInfo imageInfo) - { - ImageInfo = imageInfo; - } - internal ImageSerializationInfo(Image image) { - ImageInfo = new ImageInfo((Image)image.Clone()); + ImageInfo = new ImageInfo(image); } internal ImageSerializationInfo(Icon icon) { - ImageInfo = new ImageInfo((Icon)icon.Clone()); + ImageInfo = new ImageInfo(icon); } internal ImageSerializationInfo(Stream stream) @@ -100,7 +95,15 @@ private static void ReadMeta(BinaryReader br, ImageInfoBase imageInfo) #region Public Methods - public void Dispose() => ImageInfo.Dispose(); + public void Dispose() + { + // image was not cloned so disposing only possibly created frames + if (!ImageInfo.HasFrames) + return; + + foreach (ImageFrameInfo frame in ImageInfo.Frames!) + frame.Dispose(); + } #endregion diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializer.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializer.cs index 32beb9f..616815c 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/ImageSerializer.cs @@ -40,7 +40,7 @@ internal class ImageSerializer : VisualizerObjectSource /// /// Called when the debugged object has been replaced /// - public override object? CreateReplacementObject(object target, Stream incomingData) => SerializationHelper.DeserializeImageInfo(incomingData).GetCreateImage(); + public override object? CreateReplacementObject(object target, Stream incomingData) => SerializationHelper.DeserializeReplacementImage(incomingData); #endregion } diff --git a/KGySoft.Drawing.DebuggerVisualizers/Serialization/SerializationHelper.cs b/KGySoft.Drawing.DebuggerVisualizers/Serialization/SerializationHelper.cs index fc2ddd6..db4c90c 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Serialization/SerializationHelper.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Serialization/SerializationHelper.cs @@ -36,12 +36,6 @@ internal static class SerializationHelper { #region Internal Methods - internal static void SerializeImageInfo(ImageInfo image, Stream outgoingData) - { - using (var imageInfo = new ImageSerializationInfo(image)) - imageInfo.Write(new BinaryWriter(outgoingData)); - } - internal static void SerializeImageInfo(Image image, Stream outgoingData) { using (var imageInfo = new ImageSerializationInfo(image)) @@ -54,6 +48,12 @@ internal static void SerializeIconInfo(Icon icon, Stream outgoingData) iconInfo.Write(new BinaryWriter(outgoingData)); } + internal static void SerializeReplacementImageInfo(ImageInfo imageInfo, Stream outgoingData) + { + using (var replacementInfo = new ImageReplacementSerializationInfo(imageInfo)) + replacementInfo.Write(new BinaryWriter(outgoingData)); + } + internal static void SerializeGraphicsInfo(Graphics g, Stream outgoingData) { using (var graphicsInfo = new GraphicsSerializationInfo(g)) @@ -72,6 +72,10 @@ internal static void SerializeBitmapDataInfo(BitmapData bitmapData, Stream outgo internal static ImageInfo DeserializeImageInfo(Stream stream) => new ImageSerializationInfo(stream).ImageInfo; + internal static Image? DeserializeReplacementImage(Stream stream) => (Image?)new ImageReplacementSerializationInfo(stream).GetReplacementObject(); + + internal static Icon? DeserializeReplacementIcon(Stream stream) => (Icon?)new ImageReplacementSerializationInfo(stream).GetReplacementObject(); + internal static BitmapDataInfo DeserializeBitmapDataInfo(Stream stream) => new BitmapDataSerializationInfo(stream).BitmapDataInfo; internal static GraphicsInfo DeserializeGraphicsInfo(Stream stream) => new GraphicsSerializationInfo(stream).GraphicsInfo; @@ -88,7 +92,7 @@ internal static void WriteImage(BinaryWriter bw, Image image) bool asImage = image is Metafile // ... or is a TIFF with 48/64 BPP because saving as TIFF can preserve pixel format only if the raw format is also TIFF... || (bpp = image.GetBitsPerPixel()) > 32 && image.RawFormat.Guid == ImageFormat.Tiff.Guid - // ... or is an animated GIF, which always have 32 BPP pixel format + // ... or is an animated GIF, which always has 32 BPP pixel format || bpp == 32 && image.RawFormat.Guid == ImageFormat.Gif.Guid // ... or image is an icon - actually needed only for Windows XP to prevent error from LockBits when sizes are not recognized || image.RawFormat.Guid == ImageFormat.Icon.Guid; @@ -103,6 +107,10 @@ internal static void WriteImage(BinaryWriter bw, Image image) WriteRawBitmap((Bitmap)image, bw); } + internal static Image ReadImage(BinaryReader br) => br.ReadBoolean() + ? Image.FromStream(new MemoryStream(br.ReadBytes(br.ReadInt32()))) + : ReadRawBitmap(br); + internal static void WriteIcon(BinaryWriter bw, Icon icon) { using (var ms = new MemoryStream()) @@ -113,11 +121,7 @@ internal static void WriteIcon(BinaryWriter bw, Icon icon) } } - internal static Image ReadImage(BinaryReader br) => br.ReadBoolean() - ? Image.FromStream(new MemoryStream(br.ReadBytes(br.ReadInt32()))) - : ReadRawBitmap(br); - - internal static Icon ReadIcon(BinaryReader br) => new Icon(new MemoryStream(br.ReadBytes(br.ReadInt32()))); + internal static Icon? ReadIcon(BinaryReader br) => Icons.FromStream(new MemoryStream(br.ReadBytes(br.ReadInt32()))); #endregion diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/BitmapDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/BitmapDebuggerVisualizer.cs index 7aeefad..df0964a 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/BitmapDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/BitmapDebuggerVisualizer.cs @@ -41,16 +41,14 @@ internal sealed class BitmapDebuggerVisualizer : DialogDebuggerVisualizer /// The object provider. protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) { - using (ImageInfo imageInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData())) - { - ImageInfo? replacementObject = DebuggerHelper.DebugBitmap(imageInfo, objectProvider.IsObjectReplaceable); - if (!objectProvider.IsObjectReplaceable || replacementObject == null) - return; - - using var ms = new MemoryStream(); - SerializationHelper.SerializeImageInfo(replacementObject, ms); - objectProvider.ReplaceData(ms); - } + using ImageInfo imageInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData()); + ImageInfo? replacementObject = DebuggerHelper.DebugBitmap(imageInfo, objectProvider.IsObjectReplaceable); + if (replacementObject == null) + return; + + using var ms = new MemoryStream(); + SerializationHelper.SerializeReplacementImageInfo(replacementObject, ms); + objectProvider.ReplaceData(ms); } #endregion diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorPaletteDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorPaletteDebuggerVisualizer.cs index eb188ff..f9fbe18 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorPaletteDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ColorPaletteDebuggerVisualizer.cs @@ -19,7 +19,6 @@ using System.Diagnostics.CodeAnalysis; using System.Drawing.Imaging; using System.IO; -using KGySoft.Drawing.DebuggerVisualizers.Model; using KGySoft.Drawing.DebuggerVisualizers.Serialization; using Microsoft.VisualStudio.DebuggerVisualizers; diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/IconDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/IconDebuggerVisualizer.cs index a60cb45..54e942d 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/IconDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/IconDebuggerVisualizer.cs @@ -41,16 +41,14 @@ internal sealed class IconDebuggerVisualizer : DialogDebuggerVisualizer /// The object provider. protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) { - using (ImageInfo iconInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData())) - { - ImageInfo? replacementObject = DebuggerHelper.DebugIcon(iconInfo, objectProvider.IsObjectReplaceable); - if (!objectProvider.IsObjectReplaceable || replacementObject == null) - return; - - using var ms = new MemoryStream(); - SerializationHelper.SerializeImageInfo(replacementObject, ms); - objectProvider.ReplaceData(ms); - } + using ImageInfo iconInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData()); + ImageInfo? replacementObject = DebuggerHelper.DebugIcon(iconInfo, objectProvider.IsObjectReplaceable); + if (replacementObject == null) + return; + + using var ms = new MemoryStream(); + SerializationHelper.SerializeReplacementImageInfo(replacementObject, ms); + objectProvider.ReplaceData(ms); } #endregion diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ImageDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ImageDebuggerVisualizer.cs index cb6f0e4..8da0238 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ImageDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/ImageDebuggerVisualizer.cs @@ -41,16 +41,14 @@ internal sealed class ImageDebuggerVisualizer : DialogDebuggerVisualizer /// The object provider. protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) { - using (ImageInfo imageInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData())) - { - ImageInfo? replacementObject = DebuggerHelper.DebugImage(imageInfo, objectProvider.IsObjectReplaceable); - if (!objectProvider.IsObjectReplaceable || replacementObject == null) - return; - - using var ms = new MemoryStream(); - SerializationHelper.SerializeImageInfo(replacementObject, ms); - objectProvider.ReplaceData(ms); - } + using ImageInfo imageInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData()); + ImageInfo? replacementObject = DebuggerHelper.DebugImage(imageInfo, objectProvider.IsObjectReplaceable); + if (replacementObject == null) + return; + + using var ms = new MemoryStream(); + SerializationHelper.SerializeReplacementImageInfo(replacementObject, ms); + objectProvider.ReplaceData(ms); } #endregion diff --git a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/MetafileDebuggerVisualizer.cs b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/MetafileDebuggerVisualizer.cs index 01380ca..509b761 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/MetafileDebuggerVisualizer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/_DebuggerVisualizers/MetafileDebuggerVisualizer.cs @@ -41,16 +41,14 @@ internal sealed class MetafileDebuggerVisualizer : DialogDebuggerVisualizer /// The object provider. protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) { - using (ImageInfo imageInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData())) - { - ImageInfo? replacementObject = DebuggerHelper.DebugMetafile(imageInfo, objectProvider.IsObjectReplaceable); - if (!objectProvider.IsObjectReplaceable || replacementObject == null) - return; - - using var ms = new MemoryStream(); - SerializationHelper.SerializeImageInfo(replacementObject, ms); - objectProvider.ReplaceData(ms); - } + using ImageInfo imageInfo = SerializationHelper.DeserializeImageInfo(objectProvider.GetData()); + ImageInfo? replacementObject = DebuggerHelper.DebugMetafile(imageInfo, objectProvider.IsObjectReplaceable); + if (replacementObject == null) + return; + + using var ms = new MemoryStream(); + SerializationHelper.SerializeReplacementImageInfo(replacementObject, ms); + objectProvider.ReplaceData(ms); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/Model/ImageInfo.cs b/KGySoft.Drawing.ImagingTools/Model/ImageInfo.cs index b2afbe1..73f3b18 100644 --- a/KGySoft.Drawing.ImagingTools/Model/ImageInfo.cs +++ b/KGySoft.Drawing.ImagingTools/Model/ImageInfo.cs @@ -80,8 +80,6 @@ public sealed class ImageInfo : ImageInfoBase #region Constructors - #region Public Constructors - /// /// Initializes an empty instance of the class. /// The properties are expected to be initialized individually. Use the @@ -117,25 +115,6 @@ public ImageInfo(Icon? icon) #endregion - #region Internal Constructors - - internal ImageInfo(ImageInfo other) : base(other) - { - Type = other.Type; - if (Type == ImageInfoType.None) - return; - - if (other.Icon is Icon icon) - Icon = (Icon)icon.Clone(); - if (other.Frames is ImageFrameInfo[] frames) - Frames = frames.Select(f => new ImageFrameInfo(f)).ToArray(); - FileName = other.FileName; - } - - #endregion - - #endregion - #region Methods #region Public Methods @@ -216,11 +195,11 @@ protected override ValidationResultsCollection DoValidation() bool hasFrames = HasFrames; if (Type == ImageInfoType.Icon) { - if (Icon == null && !hasFrames) - result.AddError(nameof(Icon), PublicResources.ArgumentNull); + if (Icon == null && Image == null && !hasFrames) + result.AddError(nameof(Icon), PublicResources.PropertyNull(nameof(Icon))); } else if (Image == null && !hasFrames) - result.AddError(nameof(Image), PublicResources.ArgumentNull); + result.AddError(nameof(Image), PublicResources.PropertyNull(nameof(Image))); return result; } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs index ff5952b..8f46b3c 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs @@ -59,6 +59,7 @@ internal class ImageVisualizerViewModel : ViewModelBase, IViewModel, private readonly AllowedImageTypes imageTypes; private ImageInfo imageInfo = new ImageInfo(ImageInfoType.None); + private bool keepAliveImageInfo; private int currentFrame = -1; private bool isOpenFilterUpToDate; private Size currentResolution; @@ -384,7 +385,7 @@ protected virtual void Clear() protected override void Dispose(bool disposing) { - if (disposing) + if (disposing && !keepAliveImageInfo) imageInfo.Dispose(); base.Dispose(disposing); } @@ -1019,7 +1020,11 @@ private void RotateBitmap(RotateFlipType direction) Icon? IViewModel.GetEditedModel() => Icon?.Clone() as Icon; Bitmap? IViewModel.GetEditedModel() => Image?.Clone() as Bitmap; Metafile? IViewModel.GetEditedModel() => Image?.Clone() as Metafile; - ImageInfo IViewModel.GetEditedModel() => new ImageInfo(imageInfo); + ImageInfo IViewModel.GetEditedModel() + { + keepAliveImageInfo = true; + return imageTypes == AllowedImageTypes.Icon ? imageInfo.AsIcon() : imageInfo.AsImage(); + } #endregion diff --git a/changelog.txt b/changelog.txt index a2c1092..4a39ef2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -5,6 +5,22 @@ + New feature =============================================================================== +~~~~~~~~~ +* v2.4.0: +~~~~~~~~~ + ++ KGySoft.Drawing.ImagingTools.exe +================================== ++ Targeting also .NET 5.0 +* API changes: + * Members are annotated for using C# 8.0 nullable references + +- KGySoft.Drawing.DebuggerVisualizers.dll +========================================= +- Fixing a possible IndexOutOfRangeException when debugging an animated GIF. +* Improved performance and reduced memory consumption when serializing and deserializing debugged objects. + + ~~~~~~~~~ + v2.3.0: ~~~~~~~~~ From 8f747cbaeaf7c19f6efa282e49963e528bb23107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 9 May 2021 15:28:09 +0200 Subject: [PATCH 009/211] Fixing locking --- .../View/Controls/ImageViewer.cs | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 9518562..682fa76 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -121,39 +121,41 @@ internal PreviewGenerator(ImageViewer owner) { Debug.Assert(owner.image != null, "Image is not expected to be null here"); Image image = owner.image!; - PixelFormat pixelFormat = image.PixelFormat; - try + // Locking on display image so if it is the same as the original image, which is also locked when accessing its bitmap data + // the "bitmap region is already locked" can be avoided. Important: this cannot be ensured without locking here internally because + // OnPaint can occur any time after invalidating. + lock (image) { - // Converting non supported or too slow pixel formats - if (pixelFormat.In(convertedFormats)) + PixelFormat pixelFormat = image.PixelFormat; + + try { - // Locking on display image so if it is the same as the original image, which is also locked when accessing its bitmap data - // the "bitmap region is already locked" can be avoided. Important: this cannot be ensured without locking here internally because - // OnPaint can occur any time after invalidating. - isClonedSafeDefaultImage = true; - lock (image) + // Converting non supported or too slow pixel formats + if (pixelFormat.In(convertedFormats)) + { + isClonedSafeDefaultImage = true; safeDefaultImage = pixelFormat == PixelFormat.Format16bppGrayScale ? image.ConvertPixelFormat(PixelFormat.Format8bppIndexed, PredefinedColorsQuantizer.Grayscale()) : image.ConvertPixelFormat(pixelFormat.HasAlpha() ? PixelFormat.Format32bppPArgb : PixelFormat.Format24bppRgb); - } + } - // Raw icons: converting because icons are handled oddly by GDI+, for example, the first column has half pixel width - else if (image is Bitmap bmp && bmp.RawFormat.Guid == ImageFormat.Icon.Guid) - { - isClonedSafeDefaultImage = true; - lock (image) + // Raw icons: converting because icons are handled oddly by GDI+, for example, the first column has half pixel width + else if (image is Bitmap bmp && bmp.RawFormat.Guid == ImageFormat.Icon.Guid) + { + isClonedSafeDefaultImage = true; safeDefaultImage = bmp.CloneCurrentFrame(); + } + else + safeDefaultImage = image; } - else + catch (Exception e) when (!e.IsCriticalGdi()) + { + // It may happen if no clone could be created (maybe on low memory) + // If pixel format is not supported at all then we let rendering die; otherwise, it may work but slowly or with visual glitches + isClonedSafeDefaultImage = false; safeDefaultImage = image; - } - catch (Exception e) when (!e.IsCriticalGdi()) - { - // It may happen if no clone could be created (maybe on low memory) - // If pixel format is not supported at all then we let rendering die; otherwise, it may work but slowly or with visual glitches - isClonedSafeDefaultImage = false; - safeDefaultImage = image; + } } } From c1c58b093a244f0d1ec2c733d05e728a3c429567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 9 May 2021 17:19:46 +0200 Subject: [PATCH 010/211] Preventing possible issues when closing a form while an async task is still in progress --- .../ViewModel/CountColorsViewModel.cs | 2 +- .../ViewModel/TransformBitmapViewModelBase.cs | 32 +++++++++++++++---- .../ViewModel/ViewModelBase.cs | 24 +++++++++++++- changelog.txt | 1 + 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs index 915de12..b1cd136 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs @@ -172,7 +172,7 @@ void Action() IsProcessing = false; } - SynchronizedInvokeCallback?.Invoke(Action); + TryInvokeSync(Action); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs index 786e834..4a49c09 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs @@ -127,7 +127,7 @@ protected TransformBitmapViewModelBase(Bitmap image) internal override void ViewLoaded() { // could be in constructor but we only need it when there is a view - drawingProgressManager = new DrawingProgressManager(p => Progress = p); + drawingProgressManager = new DrawingProgressManager(TrySetProgress); initializing = false; base.ViewLoaded(); } @@ -221,7 +221,13 @@ protected override void Dispose(bool disposing) return; if (disposing) { - activeTask?.Dispose(); + if (activeTask != null) + { + CancelRunningGenerate(); + WaitForPendingGenerate(); + } + + Debug.Assert(activeTask == null); Image? preview = PreviewImageViewModel.PreviewImage; PreviewImageViewModel.Dispose(); @@ -246,7 +252,7 @@ private void DoGenerate(object state) lock (syncRoot) { // lost race - if (!MatchesSettings(task)) + if (IsDisposed || !MatchesSettings(task)) { task.Dispose(); return; @@ -266,7 +272,7 @@ private void DoGenerate(object state) // original image if (MatchesOriginal(task)) { - SynchronizedInvokeCallback?.Invoke(() => + TryInvokeSync(() => { SetPreview(originalImage); IsGenerating = false; @@ -282,7 +288,7 @@ private void DoGenerate(object state) catch (Exception e) when (!e.IsCriticalGdi()) { task.SetCompleted(); - SynchronizedInvokeCallback?.Invoke(() => + TryInvokeSync(() => { GeneratePreviewError = e; SetPreview(null); @@ -324,7 +330,7 @@ private void DoGenerate(object state) } // applying result (or error) - SynchronizedInvokeCallback?.Invoke(() => + TryInvokeSync(() => { GeneratePreviewError = error; SetPreview(result); @@ -339,6 +345,20 @@ private void DoGenerate(object state) } } + private void TrySetProgress(DrawingProgress progress) + { + if (IsDisposed) + return; + try + { + Progress = progress; + } + catch (ObjectDisposedException) + { + // lost race - just ignoring it + } + } + private void CancelRunningGenerate() { GenerateTaskBase? runningTask = activeTask; diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs index dfe3622..3d1e5dc 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs @@ -37,7 +37,7 @@ internal abstract class ViewModelBase : ObservableObjectBase, IViewModel internal Func? ConfirmCallback { get => Get?>(); set => Set(value); } internal Action? ShowChildViewCallback { get => Get?>(); set => Set(value); } internal Action? CloseViewCallback { get => Get(); set => Set(value); } - internal Action? SynchronizedInvokeCallback { get => Get?>(); set => Set(value); } + internal Action? SynchronizedInvokeCallback { private get => Get?>(); set => Set(value); } #endregion @@ -50,6 +50,28 @@ internal abstract class ViewModelBase : ObservableObjectBase, IViewModel protected void ShowInfo(string message) => ShowInfoCallback?.Invoke(message); protected bool Confirm(string message) => ConfirmCallback?.Invoke(message) ?? true; + protected bool TryInvokeSync(Action action) + { + if (IsDisposed) + return false; + + Action? callback; + try + { + callback = SynchronizedInvokeCallback; + } + catch (ObjectDisposedException) + { + return false; + } + + if (callback == null) + return false; + + callback.Invoke(action); + return true; + } + #endregion #region Internal Methods diff --git a/changelog.txt b/changelog.txt index 4a39ef2..87a0554 100644 --- a/changelog.txt +++ b/changelog.txt @@ -12,6 +12,7 @@ + KGySoft.Drawing.ImagingTools.exe ================================== + Targeting also .NET 5.0 +- Fixing possible errors when closing forms while an async operation is still in progress. * API changes: * Members are annotated for using C# 8.0 nullable references From fc4d33373cc2992d993c6db66075610a83399d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 9 May 2021 19:06:03 +0200 Subject: [PATCH 011/211] VS/Tools menu executes ImagingTools on a new thread to prevent performance issues --- .../_Classes/ExecuteImagingToolsCommand.cs | 34 +++++++++++++++++-- .../View/Forms/MvvmBaseForm.cs | 4 +-- .../View/ViewFactory.cs | 1 + changelog.txt | 7 +++- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ExecuteImagingToolsCommand.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ExecuteImagingToolsCommand.cs index e59378f..bfd9754 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ExecuteImagingToolsCommand.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ExecuteImagingToolsCommand.cs @@ -18,6 +18,7 @@ using System; using System.ComponentModel.Design; +using System.Threading; using KGySoft.Drawing.ImagingTools.View; using KGySoft.Drawing.ImagingTools.ViewModel; @@ -76,10 +77,10 @@ private static void OnExecuteImagingToolsCommand(object sender, EventArgs e) { imagingToolsViewModel?.Dispose(); imagingToolsViewModel = ViewModelFactory.CreateDefault(); - imagingToolsView = ViewFactory.CreateView(imagingToolsViewModel); + imagingToolsView = CreateViewInNewThread(imagingToolsViewModel); } - - imagingToolsView.Show(); + else + imagingToolsView.Show(); } catch (Exception ex) { @@ -90,6 +91,33 @@ private static void OnExecuteImagingToolsCommand(object sender, EventArgs e) } } + private static IView CreateViewInNewThread(IViewModel viewModel) + { + IView? result = null; + using var created = new ManualResetEvent(false); + + // Creating a non-background STA thread for the view so the possible lagging of VisualStudio will not affect its performance + var t = new Thread(() => + { + result = ViewFactory.CreateView(viewModel); + + // ReSharper disable once AccessToDisposedClosure - disposed only after awaited + created.Set(); + + // Now the view is shown as a dialog and this thread is kept alive until it is closed. + // The caller method returns once the view is created and the result is also stored and can + // be re-used until closing the view and thus exiting the thread. + result.ShowDialog(); + result.Dispose(); + }); + + t.SetApartmentState(ApartmentState.STA); + t.IsBackground = false; + t.Start(); + created.WaitOne(); + return result!; + } + #endregion #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index d83359f..72d28b9 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -152,7 +152,7 @@ private void InvokeIfRequired(Action action) void IView.ShowDialog(IntPtr ownerHandle) => ShowDialog(ownerHandle == IntPtr.Zero ? null : new OwnerWindowHandle(ownerHandle)); - void IView.Show() + void IView.Show() => InvokeIfRequired(() => { if (!Visible) { @@ -164,7 +164,7 @@ void IView.Show() WindowState = FormWindowState.Normal; Activate(); BringToFront(); - } + }); #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs b/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs index 70324d9..6a9bef9 100644 --- a/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs +++ b/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs @@ -17,6 +17,7 @@ #region Usings using System; + using KGySoft.Drawing.ImagingTools.Model; using KGySoft.Drawing.ImagingTools.View.Design; using KGySoft.Drawing.ImagingTools.View.Forms; diff --git a/changelog.txt b/changelog.txt index 87a0554..c7f7c6a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -9,7 +9,7 @@ * v2.4.0: ~~~~~~~~~ -+ KGySoft.Drawing.ImagingTools.exe +* KGySoft.Drawing.ImagingTools.exe ================================== + Targeting also .NET 5.0 - Fixing possible errors when closing forms while an async operation is still in progress. @@ -21,6 +21,11 @@ - Fixing a possible IndexOutOfRangeException when debugging an animated GIF. * Improved performance and reduced memory consumption when serializing and deserializing debugged objects. +* KGySoft.Drawing.DebuggerVisualizers.Package.dll +================================================= +* When ImagingTools is executed from Visual Studio Tools menu a new thread is created so the possible lagging of + Visual Studio does not affect the performance of ImagingTools anymore. + ~~~~~~~~~ + v2.3.0: From 85474618f3b1793be1f0bf166ee131c10a92716b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 10 May 2021 18:43:45 +0200 Subject: [PATCH 012/211] Smooth Zooming refactoring --- .../KGySoft.Drawing.ImagingTools.Messages.resx | 5 ++--- KGySoft.Drawing.ImagingTools/Res.cs | 3 +-- .../View/Controls/ImageViewer.cs | 5 +++-- .../View/Forms/ImageVisualizerForm.cs | 3 +++ .../View/Forms/TransformBitmapFormBase.cs | 3 +++ .../View/UserControls/PreviewImageControl.cs | 13 ++++++++++--- .../ViewModel/ImageVisualizerViewModel.cs | 1 - .../ViewModel/PreviewImageViewModel.cs | 4 ---- changelog.txt | 1 + 9 files changed, 23 insertions(+), 15 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index d2abd05..379cd60 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -217,8 +217,7 @@ Toggles whether the metafile is displayed with anti aliasing enabled. - Toggles whether an enlarged image is rendered with smoothing interpolation. -A shrunk image is always displayed with smoothing. + Toggles whether the enlarged or shrunk image is rendered with smoothing interpolation. Auto @@ -500,7 +499,7 @@ Dithering may help to preserve more details. Auto Zoom (Alt+Z) - Smooth Zooming + Smooth Zooming (Alt+S) Open... (Ctrl+O) diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 47c8deb..bc3d84d 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -124,8 +124,7 @@ internal static class Res /// Toggles whether the metafile is displayed with anti aliasing enabled. internal static string TooltipTextSmoothMetafile => Get("TooltipText_SmoothMetafile"); - /// Toggles whether an enlarged image is rendered with smoothing interpolation. - /// A shrunk image is always displayed with smoothing. + /// Toggles whether the enlarged or shrunk image is rendered with smoothing interpolation. internal static string TooltipTextSmoothBitmap => Get("TooltipText_SmoothBitmap"); /// Auto diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 682fa76..8d96eff 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -181,7 +181,7 @@ internal void BeginGenerateDisplayImage() Size size = owner.targetRectangle.Size; bool isGenerateNeeded = owner.isMetafile ? owner.smoothZooming - : owner.zoom < 1f && (owner.imageSize.Width >= generateThreshold || owner.imageSize.Height >= generateThreshold); + : owner.smoothZooming && owner.zoom < 1f && (owner.imageSize.Width >= generateThreshold || owner.imageSize.Height >= generateThreshold); if (!isGenerateNeeded || size.Width < 1 || size.Height < 1) { @@ -920,9 +920,10 @@ private void PaintImage(Graphics g) dest.X -= sbHorizontal.Value; if (sbVerticalVisible) dest.Y -= sbVertical.Value; - g.InterpolationMode = !isMetafile && (smoothZooming && zoom > 1f || zoom < 1f && imageSize.Width < generateThreshold && imageSize.Height < generateThreshold) ? InterpolationMode.HighQualityBicubic : InterpolationMode.NearestNeighbor; + g.InterpolationMode = !isMetafile && (smoothZooming && zoom > 1f || smoothZooming && zoom < 1f && imageSize.Width < generateThreshold && imageSize.Height < generateThreshold) ? InterpolationMode.HighQualityBicubic : InterpolationMode.NearestNeighbor; g.PixelOffsetMode = PixelOffsetMode.HighQuality; + // This lock ensures that no disposed image is painted. The generator also locks on itself when frees the cached preview. lock (previewGenerator) { // Locking on display image so if it is the same as the original image, which is also locked when accessing its bitmap data diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index 989288c..5d318a8 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -141,6 +141,9 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData) case Keys.Alt | Keys.Z: btnAutoZoom.PerformClick(); return true; + case Keys.Alt | Keys.S: + btnAntiAlias.PerformClick(); + return true; case Keys.Shift | Keys.Right: btnNext.PerformClick(); return true; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs index aae5e31..e4c6574 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs @@ -96,6 +96,9 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData) case Keys.Alt | Keys.Z: previewImage.AutoZoom = !previewImage.AutoZoom; return true; + case Keys.Alt | Keys.S: + previewImage.SmoothZooming = !previewImage.SmoothZooming; + return true; default: return base.ProcessCmdKey(ref msg, keyData); } diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs index c9a6c6b..1667692 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs @@ -50,6 +50,16 @@ internal bool AutoZoom } } + internal bool SmoothZooming + { + get => ViewModel?.SmoothZooming ?? false; + set + { + if (ViewModel is PreviewImageViewModel vm) + vm.SmoothZooming = value; + } + } + internal ImageViewer ImageViewer => ivPreview; #endregion @@ -107,9 +117,6 @@ private void InitPropertyBindings() // VM.DisplayImage -> ivPreview.Image CommandBindings.AddPropertyBinding(ViewModel!, nameof(ViewModel.DisplayImage), nameof(ivPreview.Image), ivPreview); - // VM.ZoomEnabled -> btnAutoZoom/btnAntiAlias.Enabled - CommandBindings.AddPropertyBinding(ViewModel!, nameof(ViewModel.ZoomEnabled), nameof(ToolStripItem.Enabled), btnAutoZoom, btnAntiAlias); - // VM.ShowOriginalEnabled -> btnShowOriginal.Enabled CommandBindings.AddPropertyBinding(ViewModel!, nameof(ViewModel.ShowOriginalEnabled), nameof(ToolStripItem.Enabled), btnShowOriginal); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs index 8f46b3c..6e32f37 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs @@ -886,7 +886,6 @@ private void InitAutoZoom(bool viewLoading) if (imageInfo.Type == ImageInfoType.None) { SetAutoZoomCommandState.Enabled = AutoZoom = false; - SetSmoothZoomingCommandState.Enabled = SmoothZooming = false; SetSmoothZoomingCommandState[stateToolTipText] = null; return; } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/PreviewImageViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/PreviewImageViewModel.cs index 7ec4545..8af5350 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/PreviewImageViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/PreviewImageViewModel.cs @@ -34,7 +34,6 @@ internal class PreviewImageViewModel : ViewModelBase internal bool AutoZoom { get => Get(true); set => Set(value); } internal bool SmoothZooming { get => Get(true); set => Set(value); } internal bool ShowOriginal { get => Get(); set => Set(value); } - internal bool ZoomEnabled { get => Get(); set => Set(value); } internal bool ShowOriginalEnabled { get => Get(true); set => Set(value); } #endregion @@ -56,9 +55,6 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) if (ShowOriginal) DisplayImage = PreviewImage; return; - case nameof(DisplayImage): - ZoomEnabled = e.NewValue != null; - return; case nameof(ShowOriginal): DisplayImage = e.NewValue is true ? OriginalImage : PreviewImage; return; diff --git a/changelog.txt b/changelog.txt index c7f7c6a..3e47834 100644 --- a/changelog.txt +++ b/changelog.txt @@ -13,6 +13,7 @@ ================================== + Targeting also .NET 5.0 - Fixing possible errors when closing forms while an async operation is still in progress. +* Smooth Zooming on/off affects also shrunk images (previously affected enlarged images only) * API changes: * Members are annotated for using C# 8.0 nullable references From b745e1ef0acc5a82967876d96fe45bd4240eac68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 10 May 2021 22:31:34 +0200 Subject: [PATCH 013/211] Improving image viewer performance --- .../View/Controls/ImageViewer.cs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 8d96eff..72848a8 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -132,12 +132,10 @@ internal PreviewGenerator(ImageViewer owner) try { // Converting non supported or too slow pixel formats - if (pixelFormat.In(convertedFormats)) + if (pixelFormat.In(convertedFormats) || pixelFormat != PixelFormat.Format32bppPArgb && (image.Width > generateThreshold || image.Height > generateThreshold)) { + safeDefaultImage = image.ConvertPixelFormat(PixelFormat.Format32bppPArgb); isClonedSafeDefaultImage = true; - safeDefaultImage = pixelFormat == PixelFormat.Format16bppGrayScale - ? image.ConvertPixelFormat(PixelFormat.Format8bppIndexed, PredefinedColorsQuantizer.Grayscale()) - : image.ConvertPixelFormat(pixelFormat.HasAlpha() ? PixelFormat.Format32bppPArgb : PixelFormat.Format24bppRgb); } // Raw icons: converting because icons are handled oddly by GDI+, for example, the first column has half pixel width @@ -151,10 +149,30 @@ internal PreviewGenerator(ImageViewer owner) } catch (Exception e) when (!e.IsCriticalGdi()) { - // It may happen if no clone could be created (maybe on low memory) - // If pixel format is not supported at all then we let rendering die; otherwise, it may work but slowly or with visual glitches - isClonedSafeDefaultImage = false; - safeDefaultImage = image; + // for converted formats trying again with more compact formats + if (pixelFormat.In(convertedFormats)) + { + try + { + safeDefaultImage = pixelFormat == PixelFormat.Format16bppGrayScale + ? image.ConvertPixelFormat(PixelFormat.Format8bppIndexed, PredefinedColorsQuantizer.Grayscale()) + : image.ConvertPixelFormat(pixelFormat.HasAlpha() ? PixelFormat.Format32bppPArgb : PixelFormat.Format24bppRgb); + isClonedSafeDefaultImage = true; + } + catch (Exception eInner) when (!eInner.IsCriticalGdi()) + { + // If pixel format is not supported at all then we let rendering die; otherwise, it may work but slowly or with visual glitches + isClonedSafeDefaultImage = false; + safeDefaultImage = image; + } + } + else + { + // It may happen if no clone could be created (maybe on low memory) + // Here it is not so important after all because we wanted to use it for performance reasons. + isClonedSafeDefaultImage = false; + safeDefaultImage = image; + } } } } From 6a2c3be2de75937ca38e47c8e5ea04f1f90cf22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 11 May 2021 13:36:08 +0200 Subject: [PATCH 014/211] Assertation fix --- KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 72848a8..4b0935c 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -388,7 +388,7 @@ private void DoGenerate(object state) return; Debug.WriteLine("Applying generated result"); - Debug.Assert(displayImage == null || displayImage == safeDefaultImage, "Display image is not the same as the original one: dispose is necessary"); + Debug.Assert(displayImage == null || displayImage == safeDefaultImage || displayImage == owner.image, "Display image is not the same as the original one: dispose is necessary"); // not freeing the display image because it is always the original image here displayImage = result; From 8a52704d4102a8a8b497b24ea8cc776ff072712a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 13 May 2021 11:45:24 +0200 Subject: [PATCH 015/211] Supporting panning a zoomed image by clicking and dragging --- .../Properties/Resources.resx | 6 + .../Resources/HandGrab.ico | Bin 0 -> 40889 bytes .../Resources/HandOpen.ico | Bin 0 -> 40784 bytes .../TupleElementNamesAttribute.cs | 16 +++ .../System/ValueTuple.cs | 46 +++++++ .../View/Controls/ImageViewer.Designer.cs | 7 +- .../View/Controls/ImageViewer.cs | 46 +++++-- .../View/Controls/ImageViewer.resx | 129 ++++++++++++++++++ KGySoft.Drawing.ImagingTools/View/Cursors.cs | 50 +++++++ KGySoft.Drawing.ImagingTools/View/Images.cs | 14 +- .../View/_Extensions/ScrollbarExtensions.cs | 17 +++ changelog.txt | 3 +- 12 files changed, 308 insertions(+), 26 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/Resources/HandGrab.ico create mode 100644 KGySoft.Drawing.ImagingTools/Resources/HandOpen.ico create mode 100644 KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/TupleElementNamesAttribute.cs create mode 100644 KGySoft.Drawing.ImagingTools/System/ValueTuple.cs create mode 100644 KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.resx create mode 100644 KGySoft.Drawing.ImagingTools/View/Cursors.cs create mode 100644 KGySoft.Drawing.ImagingTools/View/_Extensions/ScrollbarExtensions.cs diff --git a/KGySoft.Drawing.ImagingTools/Properties/Resources.resx b/KGySoft.Drawing.ImagingTools/Properties/Resources.resx index eebbb70..8c833d4 100644 --- a/KGySoft.Drawing.ImagingTools/Properties/Resources.resx +++ b/KGySoft.Drawing.ImagingTools/Properties/Resources.resx @@ -139,6 +139,12 @@ ..\Resources\Edit.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\HandGrab.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\HandOpen.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\HighlightVisibleClip.ico;System.Drawing.Icon, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/KGySoft.Drawing.ImagingTools/Resources/HandGrab.ico b/KGySoft.Drawing.ImagingTools/Resources/HandGrab.ico new file mode 100644 index 0000000000000000000000000000000000000000..dacadf115405caf0f34c8f9cc8ee293d88416d2c GIT binary patch literal 40889 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 literal 0 HcmV?d00001 diff --git a/KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/TupleElementNamesAttribute.cs b/KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/TupleElementNamesAttribute.cs new file mode 100644 index 0000000..3d74fe2 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/TupleElementNamesAttribute.cs @@ -0,0 +1,16 @@ +#if NET35 || NET40 || NET45 +using System.Collections.Generic; + +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Class | AttributeTargets.Struct)] + internal sealed class TupleElementNamesAttribute : Attribute + { + private readonly string[] transformNames; + public TupleElementNamesAttribute(string[] transformNames) => this.transformNames = transformNames ?? throw new ArgumentNullException(nameof(transformNames)); + public TupleElementNamesAttribute() => transformNames = new string[] { }; + public IList TransformNames => transformNames; + } +} +#endif \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/System/ValueTuple.cs b/KGySoft.Drawing.ImagingTools/System/ValueTuple.cs new file mode 100644 index 0000000..33d8152 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/System/ValueTuple.cs @@ -0,0 +1,46 @@ +#if NET35 || NET40 || NET45 +// ReSharper disable NonReadonlyMemberInGetHashCode +// ReSharper disable FieldCanBeMadeReadOnly.Global +using System.Collections.Generic; + +// ReSharper disable once CheckNamespace +namespace System +{ + internal struct ValueTuple + { + internal static int CombineHashCodes(int h1, int h2) + { + uint num = (uint)((h1 << 5) | (h1 >> 27)); + return ((int)num + h1) ^ h2; + } + } + + [Serializable] + internal struct ValueTuple : IEquatable> + { + public T1 Item1; + public T2 Item2; + + public ValueTuple(T1 item1, T2 item2) + { + Item1 = item1; + Item2 = item2; + } + + public bool Equals(ValueTuple other) + => EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2); + + public override bool Equals(object obj) => obj is ValueTuple tuple && Equals(tuple); + + public override int GetHashCode() + => ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2)); + + public override string ToString() => $"({Item1}, {Item2})"; + + public static bool operator ==(ValueTuple left, ValueTuple right) => left.Equals(right); + public static bool operator !=(ValueTuple left, ValueTuple right) => !left.Equals(right); + } +} +#endif diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.Designer.cs index 60feeb1..ed25152 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.Designer.cs @@ -11,16 +11,18 @@ private void InitializeComponent() this.sbVertical = new System.Windows.Forms.VScrollBar(); this.SuspendLayout(); // - // hScrollBar + // sbHorizontal // + this.sbHorizontal.Cursor = System.Windows.Forms.Cursors.Arrow; this.sbHorizontal.Location = new System.Drawing.Point(0, 0); this.sbHorizontal.Name = "sbHorizontal"; this.sbHorizontal.Size = new System.Drawing.Size(80, 17); this.sbHorizontal.TabIndex = 0; this.sbHorizontal.Visible = false; // - // vScrollBar + // sbVertical // + this.sbVertical.Cursor = System.Windows.Forms.Cursors.Arrow; this.sbVertical.Location = new System.Drawing.Point(0, 0); this.sbVertical.Name = "sbVertical"; this.sbVertical.Size = new System.Drawing.Size(17, 80); @@ -32,6 +34,7 @@ private void InitializeComponent() this.Controls.Add(this.sbHorizontal); this.Controls.Add(this.sbVertical); this.ResumeLayout(false); + } private HScrollBar sbHorizontal; diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 4b0935c..9e650a8 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -551,6 +551,9 @@ private void DoGenerate(object state) private int scrollFractionVertical; private int scrollFractionHorizontal; private bool isApplyingZoom; + private bool isDragging; + private Size draggingOrigin; + private Point scrollingOrigin; #endregion @@ -705,6 +708,33 @@ protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); Focus(); + if (!(sbHorizontalVisible || sbVerticalVisible) || (e.Button & MouseButtons.Left) == MouseButtons.None) + return; + isDragging = true; + draggingOrigin = new Size(e.Location); + scrollingOrigin = new Point(sbHorizontal.Value, sbVertical.Value); + Cursor = Cursors.HandGrab; + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + if ((e.Button & MouseButtons.Left) == MouseButtons.None) + return; + isDragging = false; + Cursor = sbHorizontalVisible || sbVerticalVisible ? Cursors.HandOpen : null; + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + if (!isDragging) + return; + Point distance = e.Location - draggingOrigin; + if (sbHorizontalVisible && distance.X != 0) + sbHorizontal.SetValueSafe(scrollingOrigin.X - distance.X); + if (sbVerticalVisible && distance.Y != 0) + sbVertical.SetValueSafe(scrollingOrigin.Y - distance.Y); } protected override void OnMouseWheel(MouseEventArgs e) @@ -795,12 +825,7 @@ private void VerticalScroll(int delta) int totalDelta = scrollFractionVertical + delta * sbVertical.SmallChange; scrollFractionVertical = totalDelta % SystemInformation.MouseWheelScrollDelta; int newValue = sbVertical.Value - totalDelta / SystemInformation.MouseWheelScrollDelta; - if (newValue < sbVertical.Minimum) - newValue = sbVertical.Minimum; - else if (newValue > sbVertical.Maximum - sbVertical.LargeChange + 1) - newValue = sbVertical.Maximum - sbVertical.LargeChange + 1; - - sbVertical.Value = newValue; + sbVertical.SetValueSafe(newValue); } private void HorizontalScroll(int delta) @@ -810,12 +835,7 @@ private void HorizontalScroll(int delta) int totalDelta = scrollFractionHorizontal + delta * sbVertical.SmallChange; scrollFractionHorizontal = totalDelta % SystemInformation.MouseWheelScrollDelta; int newValue = sbHorizontal.Value - totalDelta / SystemInformation.MouseWheelScrollDelta; - if (newValue < sbHorizontal.Minimum) - newValue = sbHorizontal.Minimum; - else if (newValue > sbHorizontal.Maximum - sbHorizontal.LargeChange + 1) - newValue = sbHorizontal.Maximum - sbHorizontal.LargeChange + 1; - - sbHorizontal.Value = newValue; + sbHorizontal.SetValueSafe(newValue); } private void Invalidate(InvalidateFlags flags) @@ -925,6 +945,8 @@ private void AdjustSizes() sbHorizontal.Visible = sbHorizontalVisible; sbVertical.Visible = sbVerticalVisible; + Cursor = sbHorizontalVisible || sbVerticalVisible ? Cursors.HandOpen : null; + isDragging = false; clientRectangle = new Rectangle(Point.Empty, clientSize); targetRectangle = new Rectangle(targetLocation, scaledSize); diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.resx b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.resx new file mode 100644 index 0000000..93adce0 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 122, 17 + + + 17, 17 + + + False + + \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Cursors.cs b/KGySoft.Drawing.ImagingTools/View/Cursors.cs new file mode 100644 index 0000000..7cda793 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Cursors.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Windows.Forms; +using KGySoft.Collections; + +namespace KGySoft.Drawing.ImagingTools.View +{ + internal static class Cursors + { + private class CursorInfo + { + private readonly Icon icon; + + private readonly Dictionary createdCursors = new(); + + internal CursorInfo(Icon icon) => this.icon = icon; + + internal Cursor GetCreateCursor(Size desiredSize) + { + if (createdCursors.TryGetValue(desiredSize.Width, out var value)) + return value.Cursor; + + // extracting bitmap and not icon so any sizes should work on all platforms + using Bitmap image = icon.ExtractNearestBitmap(desiredSize, PixelFormat.Format32bppArgb); + CursorHandle handle = image.ToCursorHandle(new Point(image.Width >> 1, image.Height >> 1)); + Cursor result = new Cursor(handle); + createdCursors[desiredSize.Width] = (handle, result); + return result; + } + } + + private static readonly Size referenceSize = new Size(16, 16); + + private static StringKeyedDictionary cursors = new StringKeyedDictionary(); + + + internal static Cursor HandOpen => GetCreateCursor(); + internal static Cursor HandGrab => GetCreateCursor(); + + private static Cursor GetCreateCursor([CallerMemberName]string resourceName = null!) + { + if (!cursors.TryGetValue(resourceName, out CursorInfo? info)) + cursors[resourceName] = info = new CursorInfo((Icon)Properties.Resources.ResourceManager.GetObject(resourceName, CultureInfo.InvariantCulture)!); + return info.GetCreateCursor(referenceSize.Scale(OSUtils.SystemScale)); + } + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Images.cs b/KGySoft.Drawing.ImagingTools/View/Images.cs index 85695d1..6514e42 100644 --- a/KGySoft.Drawing.ImagingTools/View/Images.cs +++ b/KGySoft.Drawing.ImagingTools/View/Images.cs @@ -29,7 +29,7 @@ internal static class Images { #region Fields - #region Private Fields + private static readonly Size referenceSize = new Size(16, 16); private static Bitmap? check; private static Bitmap? crop; @@ -56,14 +56,6 @@ internal static class Images #endregion - #region Internal Fields - - internal static readonly Size ReferenceSize = new Size(16, 16); - - #endregion - - #endregion - #region Properties internal static Bitmap Check => check ??= GetResource(nameof(Check)); @@ -99,7 +91,7 @@ internal static Bitmap ToScaledBitmap(this Icon icon) { if (icon == null) throw new ArgumentNullException(nameof(icon), PublicResources.ArgumentNull); - return icon.ExtractNearestBitmap(ReferenceSize.Scale(OSUtils.SystemScale), PixelFormat.Format32bppArgb); + return icon.ExtractNearestBitmap(referenceSize.Scale(OSUtils.SystemScale), PixelFormat.Format32bppArgb); } internal static Icon ToScaledIcon(this Icon icon, bool legacyScaling = true) @@ -107,7 +99,7 @@ internal static Icon ToScaledIcon(this Icon icon, bool legacyScaling = true) if (icon == null) throw new ArgumentNullException(nameof(icon), PublicResources.ArgumentNull); - Size size = ReferenceSize.Scale(OSUtils.SystemScale); + Size size = referenceSize.Scale(OSUtils.SystemScale); Icon result = icon.ExtractNearestIcon(size, PixelFormat.Format32bppArgb); int mod; if (!legacyScaling || OSUtils.IsWindows8OrLater || !OSUtils.IsWindows || (mod = result.Width & 0xF) == 0) diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/ScrollbarExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/ScrollbarExtensions.cs new file mode 100644 index 0000000..7902a8f --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/ScrollbarExtensions.cs @@ -0,0 +1,17 @@ +using System.Windows.Forms; + +namespace KGySoft.Drawing.ImagingTools.View +{ + internal static class ScrollbarExtensions + { + internal static void SetValueSafe(this ScrollBar scrollBar, int value) + { + if (value < scrollBar.Minimum) + value = scrollBar.Minimum; + else if (value > scrollBar.Maximum - scrollBar.LargeChange + 1) + value = scrollBar.Maximum - scrollBar.LargeChange + 1; + + scrollBar.Value = value; + } + } +} diff --git a/changelog.txt b/changelog.txt index 3e47834..1651471 100644 --- a/changelog.txt +++ b/changelog.txt @@ -12,8 +12,9 @@ * KGySoft.Drawing.ImagingTools.exe ================================== + Targeting also .NET 5.0 -- Fixing possible errors when closing forms while an async operation is still in progress. ++ Now panning a zoomed image is possible also by clicking and dragging with the mouse (besides usual scrolling). * Smooth Zooming on/off affects also shrunk images (previously affected enlarged images only) +- Fixing possible errors when closing forms while an async operation is still in progress. * API changes: * Members are annotated for using C# 8.0 nullable references From c4e8dac728414cfa65b370068180a4e8b5a78684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 13 May 2021 11:50:40 +0200 Subject: [PATCH 016/211] Smooth zooming messages --- .../KGySoft.Drawing.ImagingTools.Messages.resx | 4 ++-- KGySoft.Drawing.ImagingTools/Res.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 379cd60..33e7588 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -214,10 +214,10 @@ • When not checked, saving saves always the selected page only. - Toggles whether the metafile is displayed with anti aliasing enabled. + Smoothing Edges (Alt+S) - Toggles whether the enlarged or shrunk image is rendered with smoothing interpolation. + Smooth Zooming (Alt+S) Auto diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index bc3d84d..3cf4bdb 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -121,10 +121,10 @@ internal static class Res /// • When not checked, saving saves always the selected page only. internal static string TooltipTextCompoundMultiPage => Get("TooltipText_CompoundMultiPage"); - /// Toggles whether the metafile is displayed with anti aliasing enabled. + /// Smoothing Edges (Alt+S) internal static string TooltipTextSmoothMetafile => Get("TooltipText_SmoothMetafile"); - /// Toggles whether the enlarged or shrunk image is rendered with smoothing interpolation. + /// Smooth Zooming (Alt+S) internal static string TooltipTextSmoothBitmap => Get("TooltipText_SmoothBitmap"); /// Auto From d68dfa434ebe959a7eacbca0ff84be6db64895ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 13 May 2021 12:26:02 +0200 Subject: [PATCH 017/211] Allowing manual zooming even if Auto Zoom is turned on. --- .../View/Controls/ImageViewer.cs | 33 ++++++++++++------- .../View/Forms/ImageVisualizerForm.cs | 6 ++-- .../View/UserControls/PreviewImageControl.cs | 4 +-- changelog.txt | 1 + 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 9e650a8..f190897 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -561,6 +561,12 @@ private void DoGenerate(object state) #region Events + internal event EventHandler? AutoZoomChanged + { + add => Events.AddHandler(nameof(AutoZoomChanged), value); + remove => Events.RemoveHandler(nameof(AutoZoomChanged), value); + } + internal event EventHandler? ZoomChanged { add => Events.AddHandler(nameof(ZoomChanged), value); @@ -588,16 +594,7 @@ internal Image? Image internal bool AutoZoom { get => autoZoom; - set - { - if (autoZoom == value) - return; - autoZoom = value; - if (!autoZoom && !isMetafile) - SetZoom(1f); - - Invalidate(InvalidateFlags.Sizes | (autoZoom ? InvalidateFlags.DisplayImage : InvalidateFlags.None)); - } + set => SetAutoZoom(value, true); } internal float Zoom @@ -745,7 +742,7 @@ protected override void OnMouseWheel(MouseEventArgs e) // zoom case Keys.Control: if (autoZoom) - return; + SetAutoZoom(false, false); float delta = (float)e.Delta / SystemInformation.MouseWheelScrollDelta / 5; ApplyZoomChange(delta); break; @@ -999,6 +996,19 @@ private void ApplyZoomChange(float delta) SetZoom(zoom * delta); } + private void SetAutoZoom(bool value, bool resetIfBitmap) + { + if (autoZoom == value) + return; + autoZoom = value; + if (resetIfBitmap && !autoZoom && !isMetafile) + SetZoom(1f); + + // TODO: do not invalidate if zoom did not change + Invalidate(InvalidateFlags.Sizes | (autoZoom ? InvalidateFlags.DisplayImage : InvalidateFlags.None)); + OnAutoZoomChanged(EventArgs.Empty); + } + private void SetZoom(float value) { if (autoZoom || isApplyingZoom) @@ -1048,6 +1058,7 @@ private void SetZoom(float value) } } + private void OnAutoZoomChanged(EventArgs e) => Events.GetHandler(nameof(AutoZoomChanged))?.Invoke(this, e); private void OnZoomChanged(EventArgs e) => Events.GetHandler(nameof(ZoomChanged))?.Invoke(this, e); #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index 5d318a8..965dde0 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -193,14 +193,14 @@ private void InitPropertyBindings() // VM.InfoText -> txtInfo.Text CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.InfoText), nameof(TextBox.Text), txtInfo); - // VM.AutoZoom -> btnAutoZoom.Checked, imageViewer.AutoZoom + // imageViewer.AutoZoom <-> VM.AutoZoom -> btnAutoZoom.Checked + CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.AutoZoom), imageViewer, nameof(imageViewer.AutoZoom)); CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.AutoZoom), nameof(btnAutoZoom.Checked), btnAutoZoom); - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.AutoZoom), nameof(imageViewer.AutoZoom), imageViewer); // VM.Zoom <-> imageViewer.Zoom CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.Zoom), imageViewer, nameof(imageViewer.Zoom)); - // VM.SmoothZooming -> btnAntiAlias.Checked, imageViewer.SmoothZooming + // VM.SmoothZooming -> btnAntiAlias.Checked CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.SmoothZooming), nameof(btnAntiAlias.Checked), btnAntiAlias); CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.SmoothZooming), nameof(imageViewer.SmoothZooming), imageViewer); diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs index 1667692..43fd0c2 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs @@ -120,9 +120,9 @@ private void InitPropertyBindings() // VM.ShowOriginalEnabled -> btnShowOriginal.Enabled CommandBindings.AddPropertyBinding(ViewModel!, nameof(ViewModel.ShowOriginalEnabled), nameof(ToolStripItem.Enabled), btnShowOriginal); - // btnAutoZoom.Checked <-> VM.AutoZoom -> ivPreview.AutoZoom + // btnAutoZoom.Checked <-> VM.AutoZoom <-> ivPreview.AutoZoom CommandBindings.AddTwoWayPropertyBinding(ViewModel!, nameof(ViewModel.AutoZoom), btnAutoZoom, nameof(btnAutoZoom.Checked)); - CommandBindings.AddPropertyBinding(ViewModel!, nameof(ViewModel.AutoZoom), nameof(ivPreview.AutoZoom), ivPreview); + CommandBindings.AddTwoWayPropertyBinding(ViewModel!, nameof(ViewModel.AutoZoom), ivPreview, nameof(ivPreview.AutoZoom)); // btnAntiAlias.Checked <-> VM.SmoothZooming -> ivPreview.SmoothZooming CommandBindings.AddTwoWayPropertyBinding(ViewModel!, nameof(ViewModel.SmoothZooming), btnAntiAlias, nameof(btnAntiAlias.Checked)); diff --git a/changelog.txt b/changelog.txt index 1651471..b3dbb7f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -13,6 +13,7 @@ ================================== + Targeting also .NET 5.0 + Now panning a zoomed image is possible also by clicking and dragging with the mouse (besides usual scrolling). +* Manual zooming is enabled even if Auto Zoom is on; this turns off Auto Zoom automatically. * Smooth Zooming on/off affects also shrunk images (previously affected enlarged images only) - Fixing possible errors when closing forms while an async operation is still in progress. * API changes: From f513d238030357cd2e2820b0e5d04b3b6b0c07a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 13 May 2021 16:06:05 +0200 Subject: [PATCH 018/211] Formatting --- .../Controls/ScalingToolStripSplitButton.cs | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripSplitButton.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripSplitButton.cs new file mode 100644 index 0000000..987495a --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripSplitButton.cs @@ -0,0 +1,113 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: ScalingToolStripSplitButton.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System.Drawing; +using System.Windows.Forms; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View.Controls +{ + /// + /// A that can scale its arrow regardless of .NET version and app.config settings. + /// + internal class ScalingToolStripSplitButton : ToolStripDropDownButton + { + #region Fields + + #region Static Fields + + private static readonly Size arrowSizeUnscaled = new Size(5, 3); + private static readonly Size arrowPaddingUnscaled = new Size(2, 2); + + #endregion + + #region Instance Fields + + private Size arrowSize; + private Padding arrowPadding; + + #endregion + + #endregion + + #region Properties + + internal Size ArrowSize + { + get + { + if (!arrowSize.IsEmpty) + return arrowSize; + return arrowSize = Size.Round(Owner.ScaleSize(arrowSizeUnscaled)); + } + } + + internal Padding ArrowPadding + { + get + { + if (arrowPadding != Padding.Empty) + return arrowPadding; + var scaled = Size.Round(Owner.ScaleSize(arrowPaddingUnscaled)); + return arrowPadding = new Padding(scaled.Width, scaled.Height, scaled.Width, scaled.Height); + } + } + + internal Rectangle ArrowRectangle + { + get + { + var padding = ArrowPadding; + var size = ArrowSize; + var bounds = new Rectangle(Point.Empty, Size); + if (TextDirection == ToolStripTextDirection.Horizontal) + { + int x = size.Width + padding.Horizontal; + if (RightToLeft == RightToLeft.Yes) + return new Rectangle(padding.Left, 0, size.Width, bounds.Height); + return new Rectangle(bounds.Right - x, 0, size.Width, bounds.Height); + } + + int y = size.Height + padding.Vertical; + return new Rectangle(0, bounds.Bottom - y + padding.Top, bounds.Width - 1, size.Height); + } + } + + #endregion + + #region Methods + + public override Size GetPreferredSize(Size constrainingSize) + { + var showArrow = ShowDropDownArrow; + ShowDropDownArrow = false; + var preferredSize = base.GetPreferredSize(constrainingSize); + if (!showArrow) + return preferredSize; + ShowDropDownArrow = true; + if (TextDirection == ToolStripTextDirection.Horizontal) + preferredSize.Width += ArrowSize.Width + ArrowPadding.Horizontal; + else + preferredSize.Height += ArrowSize.Height + ArrowPadding.Vertical; + return preferredSize; + } + + #endregion + } +} From 2fb87a4f7f3d0fd9c2d5fdd30e7c977d5a243b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 13 May 2021 16:45:36 +0200 Subject: [PATCH 019/211] Analyzer warnings fixes --- .../_Attributes/ProvideAutoLoadAsyncAttribute.cs | 2 +- .../_Classes/DebuggerVisualizersPackage.cs | 9 ++------- KGySoft.Drawing.Tools.sln.DotSettings | 1 + 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Attributes/ProvideAutoLoadAsyncAttribute.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Attributes/ProvideAutoLoadAsyncAttribute.cs index 8e30c24..a4cfb17 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/_Attributes/ProvideAutoLoadAsyncAttribute.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Attributes/ProvideAutoLoadAsyncAttribute.cs @@ -41,7 +41,7 @@ internal sealed class ProvideAutoLoadAsyncAttribute : RegistrationAttribute #region Properties - private string RegKeyName => $"AutoLoadPackages\\{new Guid(VSConstants.UICONTEXT.NoSolution_string):B}"; + private static string RegKeyName => $"AutoLoadPackages\\{new Guid(VSConstants.UICONTEXT.NoSolution_string):B}"; #endregion diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/DebuggerVisualizersPackage.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/DebuggerVisualizersPackage.cs index 24f1890..12ebd56 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/DebuggerVisualizersPackage.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/DebuggerVisualizersPackage.cs @@ -101,7 +101,8 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); if (!disposing) return; - DestroyCommands(); + ExecuteImagingToolsCommand.DestroyCommand(); + ManageDebuggerVisualizerInstallationsCommand.DestroyCommand(); } #endregion @@ -170,12 +171,6 @@ private void InitCommands(IVsShell? shellService, IMenuCommandService? menuComma menuCommandService.AddCommand(ManageDebuggerVisualizerInstallationsCommand.GetCreateCommand(this, shellService)); } - private void DestroyCommands() - { - ExecuteImagingToolsCommand.DestroyCommand(); - ManageDebuggerVisualizerInstallationsCommand.DestroyCommand(); - } - #endregion #endregion diff --git a/KGySoft.Drawing.Tools.sln.DotSettings b/KGySoft.Drawing.Tools.sln.DotSettings index 9e6e84d..ed204ad 100644 --- a/KGySoft.Drawing.Tools.sln.DotSettings +++ b/KGySoft.Drawing.Tools.sln.DotSettings @@ -1,6 +1,7 @@  HINT SUGGESTION + ERROR DO_NOT_SHOW HINT HINT From 2eeccbb7ce3d8c695db47c18d6adf7b1dd695610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 13 May 2021 18:34:31 +0200 Subject: [PATCH 020/211] Fixing cursor update --- KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index f190897..311217f 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -874,6 +874,7 @@ private void AdjustSizes() targetRectangle = new Rectangle(targetLocation, scaledSize); clientRectangle = new Rectangle(Point.Empty, clientSize); sbHorizontal.Visible = sbVertical.Visible = sbHorizontalVisible = sbVerticalVisible = false; + Cursor = null; return; } @@ -1004,7 +1005,6 @@ private void SetAutoZoom(bool value, bool resetIfBitmap) if (resetIfBitmap && !autoZoom && !isMetafile) SetZoom(1f); - // TODO: do not invalidate if zoom did not change Invalidate(InvalidateFlags.Sizes | (autoZoom ? InvalidateFlags.DisplayImage : InvalidateFlags.None)); OnAutoZoomChanged(EventArgs.Empty); } From e7df68952e92efeda763a9e22289741b8903c3b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 13 May 2021 18:34:46 +0200 Subject: [PATCH 021/211] Formatting --- .../View/_Extensions/ScrollbarExtensions.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/ScrollbarExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/ScrollbarExtensions.cs index 7902a8f..652a62e 100644 --- a/KGySoft.Drawing.ImagingTools/View/_Extensions/ScrollbarExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/ScrollbarExtensions.cs @@ -1,9 +1,31 @@ -using System.Windows.Forms; +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: ScrollbarExtensions.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System.Windows.Forms; + +#endregion namespace KGySoft.Drawing.ImagingTools.View { internal static class ScrollbarExtensions { + #region Methods + internal static void SetValueSafe(this ScrollBar scrollBar, int value) { if (value < scrollBar.Minimum) @@ -13,5 +35,7 @@ internal static void SetValueSafe(this ScrollBar scrollBar, int value) scrollBar.Value = value; } + + #endregion } -} +} \ No newline at end of file From 5347fdf30b720e49f7af7c507533f355f249cb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 13 May 2021 18:35:18 +0200 Subject: [PATCH 022/211] Remove accidentally added file --- .../Controls/ScalingToolStripSplitButton.cs | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripSplitButton.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripSplitButton.cs deleted file mode 100644 index 987495a..0000000 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripSplitButton.cs +++ /dev/null @@ -1,113 +0,0 @@ -#region Copyright - -/////////////////////////////////////////////////////////////////////////////// -// File: ScalingToolStripSplitButton.cs -/////////////////////////////////////////////////////////////////////////////// -// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved -// -// You should have received a copy of the LICENSE file at the top-level -// directory of this distribution. If not, then this file is considered as -// an illegal copy. -// -// Unauthorized copying of this file, via any medium is strictly prohibited. -/////////////////////////////////////////////////////////////////////////////// - -#endregion - -#region Usings - -using System.Drawing; -using System.Windows.Forms; - -#endregion - -namespace KGySoft.Drawing.ImagingTools.View.Controls -{ - /// - /// A that can scale its arrow regardless of .NET version and app.config settings. - /// - internal class ScalingToolStripSplitButton : ToolStripDropDownButton - { - #region Fields - - #region Static Fields - - private static readonly Size arrowSizeUnscaled = new Size(5, 3); - private static readonly Size arrowPaddingUnscaled = new Size(2, 2); - - #endregion - - #region Instance Fields - - private Size arrowSize; - private Padding arrowPadding; - - #endregion - - #endregion - - #region Properties - - internal Size ArrowSize - { - get - { - if (!arrowSize.IsEmpty) - return arrowSize; - return arrowSize = Size.Round(Owner.ScaleSize(arrowSizeUnscaled)); - } - } - - internal Padding ArrowPadding - { - get - { - if (arrowPadding != Padding.Empty) - return arrowPadding; - var scaled = Size.Round(Owner.ScaleSize(arrowPaddingUnscaled)); - return arrowPadding = new Padding(scaled.Width, scaled.Height, scaled.Width, scaled.Height); - } - } - - internal Rectangle ArrowRectangle - { - get - { - var padding = ArrowPadding; - var size = ArrowSize; - var bounds = new Rectangle(Point.Empty, Size); - if (TextDirection == ToolStripTextDirection.Horizontal) - { - int x = size.Width + padding.Horizontal; - if (RightToLeft == RightToLeft.Yes) - return new Rectangle(padding.Left, 0, size.Width, bounds.Height); - return new Rectangle(bounds.Right - x, 0, size.Width, bounds.Height); - } - - int y = size.Height + padding.Vertical; - return new Rectangle(0, bounds.Bottom - y + padding.Top, bounds.Width - 1, size.Height); - } - } - - #endregion - - #region Methods - - public override Size GetPreferredSize(Size constrainingSize) - { - var showArrow = ShowDropDownArrow; - ShowDropDownArrow = false; - var preferredSize = base.GetPreferredSize(constrainingSize); - if (!showArrow) - return preferredSize; - ShowDropDownArrow = true; - if (TextDirection == ToolStripTextDirection.Horizontal) - preferredSize.Width += ArrowSize.Width + ArrowPadding.Horizontal; - else - preferredSize.Height += ArrowSize.Height + ArrowPadding.Vertical; - return preferredSize; - } - - #endregion - } -} From 9fac2995c114064ec72354b39d8c2e61af030ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 13 May 2021 20:31:03 +0200 Subject: [PATCH 023/211] Fixing custom cursors for Linux --- KGySoft.Drawing.ImagingTools/View/Cursors.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Cursors.cs b/KGySoft.Drawing.ImagingTools/View/Cursors.cs index 7cda793..ad01ed3 100644 --- a/KGySoft.Drawing.ImagingTools/View/Cursors.cs +++ b/KGySoft.Drawing.ImagingTools/View/Cursors.cs @@ -34,14 +34,16 @@ internal Cursor GetCreateCursor(Size desiredSize) private static readonly Size referenceSize = new Size(16, 16); - private static StringKeyedDictionary cursors = new StringKeyedDictionary(); + private static readonly StringKeyedDictionary cursors = new StringKeyedDictionary(); - internal static Cursor HandOpen => GetCreateCursor(); - internal static Cursor HandGrab => GetCreateCursor(); + internal static Cursor HandOpen => GetCreateCursor() ?? System.Windows.Forms.Cursors.Hand; + internal static Cursor HandGrab => GetCreateCursor() ?? System.Windows.Forms.Cursors.NoMove2D; - private static Cursor GetCreateCursor([CallerMemberName]string resourceName = null!) + private static Cursor? GetCreateCursor([CallerMemberName]string resourceName = null!) { + if (!OSUtils.IsWindows) + return null; if (!cursors.TryGetValue(resourceName, out CursorInfo? info)) cursors[resourceName] = info = new CursorInfo((Icon)Properties.Resources.ResourceManager.GetObject(resourceName, CultureInfo.InvariantCulture)!); return info.GetCreateCursor(referenceSize.Scale(OSUtils.SystemScale)); From 9b86b94d5ff55886d0298017f2d46600944ccbe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 13 May 2021 20:32:05 +0200 Subject: [PATCH 024/211] Adding CheckableToolStripSplitButton with corresponding renderer --- .../Controls/CheckableToolStripSplitButton.cs | 84 +++++++++++++++++++ .../View/Controls/ScalingToolStrip.cs | 65 ++++++++++---- 2 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs new file mode 100644 index 0000000..9191233 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs @@ -0,0 +1,84 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: CheckableToolStripSplitButton.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +using System.ComponentModel; +using System.Windows.Forms; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View.Controls +{ + /// + /// A whose button part can be checked. + /// + // NOTE: Unlike ToolStripDropDownButton, ToolStripSplitButton is scaled well so no special handling is needed here + // The properly scaled arrow and the checked appearance is rendered by ScalingToolStripMenuRenderer + internal class CheckableToolStripSplitButton : ToolStripSplitButton + { + #region Fields + + private bool isChecked; + + #endregion + + #region Properties + + [DefaultValue(false)] + public bool CheckOnClick { get; set; } + + [DefaultValue(false)] + public bool Checked + { + get => isChecked; + set + { + if (value == isChecked) + return; + isChecked = value; + OnCheckedChanged(EventArgs.Empty); + Invalidate(); + } + } + + #endregion + + #region Events + + public event EventHandler CheckedChanged + { + add => Events.AddHandler(nameof(CheckedChanged), value); + remove => Events.RemoveHandler(nameof(CheckedChanged), value); + } + + #endregion + + #region Methods + + protected override void OnButtonClick(EventArgs e) + { + if (CheckOnClick) + Checked = !Checked; + base.OnButtonClick(e); + } + + protected virtual void OnCheckedChanged(EventArgs e) => (Events[nameof(CheckedChanged)] as EventHandler)?.Invoke(this, e); + + #endregion + } +} diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs index bfcb824..9d0608e 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs @@ -47,11 +47,26 @@ private class ScalingToolStripMenuRenderer : ToolStripProfessionalRenderer #region Methods + #region Static Methods + + private static void ClearButtonBackground(Graphics g, Rectangle rect, Color color) + { + GraphicsState state = g.Save(); + rect.Inflate(1, 1); + g.SetClip(rect); + g.Clear(color); + g.Restore(state); + } + + #endregion + + #region Instance Methods + protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) { Graphics g = e.Graphics; Rectangle dropDownRect = e.Item is ScalingToolStripDropDownButton scalingButton ? scalingButton.ArrowRectangle : e.ArrowRectangle; - using (Brush brush = new SolidBrush(e.ArrowColor)) + using (Brush brush = new SolidBrush(e.Item.Enabled ? e.ArrowColor : SystemColors.ControlDark)) { Point middle = new Point(dropDownRect.Left + dropDownRect.Width / 2, dropDownRect.Top + dropDownRect.Height / 2); @@ -118,26 +133,46 @@ protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e) { - if (e.Item is ToolStripButton button && button.Checked && button.Enabled) + if (e.Item is ToolStripButton { Checked: true, Enabled: true } btn) + ClearButtonBackground(e.Graphics, btn.ContentRectangle, ProfessionalColors.ButtonSelectedGradientMiddle); + + base.OnRenderButtonBackground(e); + } + + protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventArgs e) + { + base.OnRenderSplitButtonBackground(e); + if (e.Item is not CheckableToolStripSplitButton btn) + return; + + // overriding background to behave the same way as ToolStripButton + Rectangle rect = btn.ButtonBounds; + if (!OSUtils.IsWindows) + rect.Location = Point.Empty; + + if (btn.Enabled && (btn.Checked || !btn.DropDownButtonPressed)) { - if (OSUtils.IsWindows) - e.Graphics.Clear(ProfessionalColors.ButtonSelectedGradientMiddle); - else - { - // In Mono without this clipping the whole tool strip container is cleared - GraphicsState state = e.Graphics.Save(); - Rectangle rect = e.Item.ContentRectangle; - rect.Inflate(1, 1); - e.Graphics.SetClip(rect); - e.Graphics.Clear(ProfessionalColors.ButtonSelectedGradientMiddle); - e.Graphics.Restore(state); - } + rect.Inflate(-1, -1); + if (btn.ButtonPressed) + ClearButtonBackground(e.Graphics, rect, ProfessionalColors.ButtonPressedHighlight); + else if (btn.Selected) + ClearButtonBackground(e.Graphics, rect, btn.Checked ? ProfessionalColors.ButtonPressedHighlight : ProfessionalColors.ButtonSelectedGradientMiddle); + else if (btn.Checked) + ClearButtonBackground(e.Graphics, rect, ProfessionalColors.ButtonSelectedGradientMiddle); + rect.Inflate(1, 1); } - base.OnRenderButtonBackground(e); + // drawing border (maybe again, because it can be overridden by background) + if (btn.Checked || !btn.DropDownButtonPressed && (btn.ButtonPressed || btn.ButtonSelected)) + { + using (Pen pen = new Pen(ProfessionalColors.ButtonSelectedBorder)) + e.Graphics.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height - 1); + } } #endregion + + #endregion } #endregion From 33cb403571c642fca8225aea65cac03c9d7040ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 13 May 2021 20:33:14 +0200 Subject: [PATCH 025/211] ImageVisualizerForm: Replacing AutoZoom button by a split button --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 6 ++++ .../KGySoft.Drawing.ImagingTools.csproj | 3 ++ .../Forms/ImageVisualizerForm.Designer.cs | 30 +++++++++++++------ .../View/Forms/ImageVisualizerForm.cs | 16 +++++----- 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 33e7588..f4fffc4 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -498,6 +498,12 @@ Dithering may help to preserve more details. Auto Zoom (Alt+Z) + + Zoom Options + + + Auto Zoom + Smooth Zooming (Alt+S) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj index e4d3d83..c000d5c 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj @@ -39,6 +39,9 @@ PublicResXFileCodeGenerator + + Component + diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs index 091882a..2aeac67 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs @@ -27,7 +27,8 @@ private void InitializeComponent() this.lblNotification = new KGySoft.Drawing.ImagingTools.View.Controls.NotificationLabel(); this.splitter = new System.Windows.Forms.Splitter(); this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStrip(); - this.btnAutoZoom = new System.Windows.Forms.ToolStripButton(); + this.btnZoom = new KGySoft.Drawing.ImagingTools.View.Controls.CheckableToolStripSplitButton(); + this.miAutoZoom = new System.Windows.Forms.ToolStripMenuItem(); this.btnAntiAlias = new System.Windows.Forms.ToolStripButton(); this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); this.btnOpen = new System.Windows.Forms.ToolStripButton(); @@ -97,7 +98,7 @@ private void InitializeComponent() // tsMenu // this.tsMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.btnAutoZoom, + this.btnZoom, this.btnAntiAlias, this.toolStripSeparator1, this.btnOpen, @@ -116,13 +117,23 @@ private void InitializeComponent() this.tsMenu.Size = new System.Drawing.Size(334, 25); this.tsMenu.TabIndex = 2; // - // btnAutoZoom + // btnZoom // - this.btnAutoZoom.CheckOnClick = true; - this.btnAutoZoom.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.btnAutoZoom.ImageTransparentColor = System.Drawing.Color.Magenta; - this.btnAutoZoom.Name = "btnAutoZoom"; - this.btnAutoZoom.Size = new System.Drawing.Size(23, 22); + this.btnZoom.CheckOnClick = true; + this.btnZoom.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnZoom.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.miAutoZoom}); + this.btnZoom.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnZoom.Name = "btnZoom"; + this.btnZoom.Size = new System.Drawing.Size(16, 22); + // + // miAutoZoom + // + this.miAutoZoom.CheckOnClick = true; + this.miAutoZoom.Name = "miAutoZoom"; + this.miAutoZoom.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Alt | System.Windows.Forms.Keys.Z))); + this.miAutoZoom.Size = new System.Drawing.Size(183, 22); + this.miAutoZoom.Text = "miAutoZoom"; // // btnAntiAlias // @@ -375,7 +386,7 @@ private void InitializeComponent() #endregion private KGySoft.Drawing.ImagingTools.View.Controls.ImageViewer imageViewer; - private System.Windows.Forms.ToolStripButton btnAutoZoom; + private CheckableToolStripSplitButton btnZoom; private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; private System.Windows.Forms.ToolStripButton btnSave; private System.Windows.Forms.ToolStripButton btnOpen; @@ -412,5 +423,6 @@ private void InitializeComponent() private ToolStripMenuItem miBrightness; private ToolStripMenuItem miContrast; private ToolStripMenuItem miGamma; + private ToolStripMenuItem miAutoZoom; } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index 965dde0..24fc04c 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -90,7 +90,7 @@ protected override void ApplyResources() // applying static resources base.ApplyResources(); Icon = Properties.Resources.ImagingTools; - btnAutoZoom.Image = Images.Magnifier; + miAutoZoom.Image = btnZoom.Image = Images.Magnifier; btnOpen.Image = Images.Open; btnSave.Image = Images.Save; btnClear.Image = Images.Clear; @@ -138,9 +138,6 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData) case Keys.Control | Keys.Delete: btnClear.PerformClick(); return true; - case Keys.Alt | Keys.Z: - btnAutoZoom.PerformClick(); - return true; case Keys.Alt | Keys.S: btnAntiAlias.PerformClick(); return true; @@ -193,9 +190,9 @@ private void InitPropertyBindings() // VM.InfoText -> txtInfo.Text CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.InfoText), nameof(TextBox.Text), txtInfo); - // imageViewer.AutoZoom <-> VM.AutoZoom -> btnAutoZoom.Checked + // imageViewer.AutoZoom <-> VM.AutoZoom -> btnZoom.Checked, miAutoZoom.Checked CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.AutoZoom), imageViewer, nameof(imageViewer.AutoZoom)); - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.AutoZoom), nameof(btnAutoZoom.Checked), btnAutoZoom); + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.AutoZoom), nameof(btnZoom.Checked), btnZoom, miAutoZoom); // VM.Zoom <-> imageViewer.Zoom CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.Zoom), imageViewer, nameof(imageViewer.Zoom)); @@ -229,8 +226,11 @@ private void InitCommandBindings() { // View CommandBindings.Add(ViewModel.SetAutoZoomCommand, ViewModel.SetAutoZoomCommandState) - .WithParameter(() => btnAutoZoom.Checked) - .AddSource(btnAutoZoom, nameof(btnAutoZoom.CheckedChanged)); + .WithParameter(() => btnZoom.Checked) + .AddSource(btnZoom, nameof(btnZoom.CheckedChanged)); + CommandBindings.Add(ViewModel.SetAutoZoomCommand, ViewModel.SetAutoZoomCommandState) + .WithParameter(() => miAutoZoom.Checked) + .AddSource(miAutoZoom, nameof(miAutoZoom.CheckedChanged)); CommandBindings.Add(ViewModel.SetSmoothZoomingCommand, ViewModel.SetSmoothZoomingCommandState) .WithParameter(() => btnAntiAlias.Checked) .AddSource(btnAntiAlias, nameof(btnAntiAlias.CheckedChanged)); From abf13b617d3fa7cec9d34d88f8adada7dffe8bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 16 May 2021 12:49:31 +0200 Subject: [PATCH 026/211] Increase/Decrease/Reset Zoom from menu --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 18 ++- .../Properties/Resources.resx | 9 ++ .../Resources/Magnifier1.ico | Bin 0 -> 62388 bytes .../Resources/MagnifierMinus.ico | Bin 0 -> 60292 bytes .../Resources/MagnifierPlus.ico | Bin 0 -> 60328 bytes .../View/Controls/ImageViewer.cs | 22 ++- .../View/Controls/ImageViewer.resx | 129 ------------------ .../View/Controls/ZoomSplitButton.cs | 93 +++++++++++++ .../Forms/ImageVisualizerForm.Designer.cs | 16 +-- .../View/Forms/ImageVisualizerForm.cs | 15 +- .../View/Forms/TransformBitmapFormBase.cs | 3 - KGySoft.Drawing.ImagingTools/View/Images.cs | 6 + .../PreviewImageControl.Designer.cs | 16 +-- .../View/UserControls/PreviewImageControl.cs | 9 +- 14 files changed, 167 insertions(+), 169 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/Resources/Magnifier1.ico create mode 100644 KGySoft.Drawing.ImagingTools/Resources/MagnifierMinus.ico create mode 100644 KGySoft.Drawing.ImagingTools/Resources/MagnifierPlus.ico delete mode 100644 KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.resx create mode 100644 KGySoft.Drawing.ImagingTools/View/Controls/ZoomSplitButton.cs diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index f4fffc4..68b75c7 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -217,7 +217,7 @@ Smoothing Edges (Alt+S) - Smooth Zooming (Alt+S) + Smoothing Resized Image (Alt+S) Auto @@ -495,17 +495,23 @@ Dithering may help to preserve more details. Installed: {0} - Target: {1} - - Auto Zoom (Alt+Z) - - Zoom Options + Auto Zoom (Alt+Z) / Zoom Options Auto Zoom + + Zoom In + + + Zoom Out + + + Actual Size + - Smooth Zooming (Alt+S) + Smoothing Resized Image (Alt+S) Open... (Ctrl+O) diff --git a/KGySoft.Drawing.ImagingTools/Properties/Resources.resx b/KGySoft.Drawing.ImagingTools/Properties/Resources.resx index 8c833d4..e7761b7 100644 --- a/KGySoft.Drawing.ImagingTools/Properties/Resources.resx +++ b/KGySoft.Drawing.ImagingTools/Properties/Resources.resx @@ -154,6 +154,15 @@ ..\Resources\Magnifier.ico;System.Drawing.Icon, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\Magnifier1.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\MagnifierMinus.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\MagnifierPlus.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\MultiPage.ico;System.Drawing.Icon, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/KGySoft.Drawing.ImagingTools/Resources/Magnifier1.ico b/KGySoft.Drawing.ImagingTools/Resources/Magnifier1.ico new file mode 100644 index 0000000000000000000000000000000000000000..89efb29aaa9d09ef8fe5fefd663ca0233b39a20f GIT binary patch literal 62388 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 K4ZneTZ1+EoE1{tP literal 0 HcmV?d00001 diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 311217f..3e7a491 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -784,7 +784,7 @@ protected override void Dispose(bool disposing) #region Internal Methods /// - /// Should be called when image content is changed + /// Should be called when image content is changed while image reference remains the same (eg. rotation, palette change) /// internal void UpdateImage() { @@ -798,6 +798,26 @@ internal void UpdateImage() Invalidate(); } + internal void IncreaseZoom() + { + SetAutoZoom(false, false); + ApplyZoomChange(0.25f); + } + + internal void DecreaseZoom() + { + SetAutoZoom(false, false); + ApplyZoomChange(-0.25f); + } + + internal void ResetZoom() + { + if (zoom.Equals(1f)) + return; + AutoZoom = false; + Zoom = 1f; + } + #endregion #region Private Methods diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.resx b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.resx deleted file mode 100644 index 93adce0..0000000 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.resx +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 122, 17 - - - 17, 17 - - - False - - \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ZoomSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ZoomSplitButton.cs new file mode 100644 index 0000000..e0d7c17 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ZoomSplitButton.cs @@ -0,0 +1,93 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: ZoomSplitButton.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +using System.Windows.Forms; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View.Controls +{ + internal class ZoomSplitButton : CheckableToolStripSplitButton + { + #region Properties + + internal ToolStripMenuItem AutoZoomMenuItem { get; } + internal ToolStripMenuItem IncreaseZoomMenuItem { get; } + internal ToolStripMenuItem DecreaseZoomMenuItem { get; } + internal ToolStripMenuItem ResetZoomMenuItem { get; } + + #endregion + + #region Constructors + + public ZoomSplitButton() + { + CheckOnClick = true; + Image = Images.Magnifier; + ToolStripItemCollection items = DropDownItems; + + AutoZoomMenuItem = new ToolStripMenuItem + { + Name = "miAutoZoom", + Image = Images.Magnifier, + CheckOnClick = true, + ShortcutKeys = Keys.Alt | Keys.Z + }; + AutoZoomMenuItem.CheckedChanged += (_, _) => Checked = AutoZoomMenuItem.Checked; + + IncreaseZoomMenuItem = new ToolStripMenuItem + { + Name = "miIncreaseZoom", + Image = Images.MagnifierPlus, + ShortcutKeys = Keys.Control | Keys.Add, + ShortcutKeyDisplayString = @"Ctrl++", + }; + + DecreaseZoomMenuItem = new ToolStripMenuItem + { + Name = "miDecreaseZoom", + Image = Images.MagnifierMinus, + ShortcutKeys = Keys.Control | Keys.Subtract, + ShortcutKeyDisplayString = @"Ctrl+-", + }; + + ResetZoomMenuItem = new ToolStripMenuItem + { + Name = "miResetZoom", + Image = Images.Magnifier1, + ShortcutKeys = Keys.Control | Keys.NumPad0, + ShortcutKeyDisplayString = @"Ctrl+0", + }; + + items.AddRange(new ToolStripItem[] { AutoZoomMenuItem, IncreaseZoomMenuItem, DecreaseZoomMenuItem, ResetZoomMenuItem }); + } + + #endregion + + #region Methods + + protected override void OnCheckedChanged(EventArgs e) + { + base.OnCheckedChanged(e); + AutoZoomMenuItem.Checked = Checked; + } + + #endregion + } +} diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs index 2aeac67..d6833fb 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs @@ -27,8 +27,7 @@ private void InitializeComponent() this.lblNotification = new KGySoft.Drawing.ImagingTools.View.Controls.NotificationLabel(); this.splitter = new System.Windows.Forms.Splitter(); this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStrip(); - this.btnZoom = new KGySoft.Drawing.ImagingTools.View.Controls.CheckableToolStripSplitButton(); - this.miAutoZoom = new System.Windows.Forms.ToolStripMenuItem(); + this.btnZoom = new KGySoft.Drawing.ImagingTools.View.Controls.ZoomSplitButton(); this.btnAntiAlias = new System.Windows.Forms.ToolStripButton(); this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); this.btnOpen = new System.Windows.Forms.ToolStripButton(); @@ -121,20 +120,10 @@ private void InitializeComponent() // this.btnZoom.CheckOnClick = true; this.btnZoom.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.btnZoom.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.miAutoZoom}); this.btnZoom.ImageTransparentColor = System.Drawing.Color.Magenta; this.btnZoom.Name = "btnZoom"; this.btnZoom.Size = new System.Drawing.Size(16, 22); // - // miAutoZoom - // - this.miAutoZoom.CheckOnClick = true; - this.miAutoZoom.Name = "miAutoZoom"; - this.miAutoZoom.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Alt | System.Windows.Forms.Keys.Z))); - this.miAutoZoom.Size = new System.Drawing.Size(183, 22); - this.miAutoZoom.Text = "miAutoZoom"; - // // btnAntiAlias // this.btnAntiAlias.CheckOnClick = true; @@ -386,7 +375,7 @@ private void InitializeComponent() #endregion private KGySoft.Drawing.ImagingTools.View.Controls.ImageViewer imageViewer; - private CheckableToolStripSplitButton btnZoom; + private KGySoft.Drawing.ImagingTools.View.Controls.ZoomSplitButton btnZoom; private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; private System.Windows.Forms.ToolStripButton btnSave; private System.Windows.Forms.ToolStripButton btnOpen; @@ -423,6 +412,5 @@ private void InitializeComponent() private ToolStripMenuItem miBrightness; private ToolStripMenuItem miContrast; private ToolStripMenuItem miGamma; - private ToolStripMenuItem miAutoZoom; } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index 24fc04c..ab98679 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -18,6 +18,7 @@ using System; using System.Drawing; +using System.Globalization; using System.Windows.Forms; using KGySoft.ComponentModel; @@ -90,7 +91,6 @@ protected override void ApplyResources() // applying static resources base.ApplyResources(); Icon = Properties.Resources.ImagingTools; - miAutoZoom.Image = btnZoom.Image = Images.Magnifier; btnOpen.Image = Images.Open; btnSave.Image = Images.Save; btnClear.Image = Images.Clear; @@ -190,9 +190,9 @@ private void InitPropertyBindings() // VM.InfoText -> txtInfo.Text CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.InfoText), nameof(TextBox.Text), txtInfo); - // imageViewer.AutoZoom <-> VM.AutoZoom -> btnZoom.Checked, miAutoZoom.Checked + // imageViewer.AutoZoom <-> VM.AutoZoom -> btnZoom.Checked CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.AutoZoom), imageViewer, nameof(imageViewer.AutoZoom)); - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.AutoZoom), nameof(btnZoom.Checked), btnZoom, miAutoZoom); + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.AutoZoom), nameof(btnZoom.Checked), btnZoom); // VM.Zoom <-> imageViewer.Zoom CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.Zoom), imageViewer, nameof(imageViewer.Zoom)); @@ -228,9 +228,12 @@ private void InitCommandBindings() CommandBindings.Add(ViewModel.SetAutoZoomCommand, ViewModel.SetAutoZoomCommandState) .WithParameter(() => btnZoom.Checked) .AddSource(btnZoom, nameof(btnZoom.CheckedChanged)); - CommandBindings.Add(ViewModel.SetAutoZoomCommand, ViewModel.SetAutoZoomCommandState) - .WithParameter(() => miAutoZoom.Checked) - .AddSource(miAutoZoom, nameof(miAutoZoom.CheckedChanged)); + CommandBindings.Add(imageViewer.IncreaseZoom) + .AddSource(btnZoom.IncreaseZoomMenuItem, nameof(btnZoom.IncreaseZoomMenuItem.Click)); + CommandBindings.Add(imageViewer.DecreaseZoom) + .AddSource(btnZoom.DecreaseZoomMenuItem, nameof(btnZoom.DecreaseZoomMenuItem.Click)); + CommandBindings.Add(imageViewer.ResetZoom) + .AddSource(btnZoom.ResetZoomMenuItem, nameof(btnZoom.ResetZoomMenuItem.Click)); CommandBindings.Add(ViewModel.SetSmoothZoomingCommand, ViewModel.SetSmoothZoomingCommandState) .WithParameter(() => btnAntiAlias.Checked) .AddSource(btnAntiAlias, nameof(btnAntiAlias.CheckedChanged)); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs index e4c6574..e812a00 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs @@ -93,9 +93,6 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { switch (keyData) { - case Keys.Alt | Keys.Z: - previewImage.AutoZoom = !previewImage.AutoZoom; - return true; case Keys.Alt | Keys.S: previewImage.SmoothZooming = !previewImage.SmoothZooming; return true; diff --git a/KGySoft.Drawing.ImagingTools/View/Images.cs b/KGySoft.Drawing.ImagingTools/View/Images.cs index 6514e42..a6c30d2 100644 --- a/KGySoft.Drawing.ImagingTools/View/Images.cs +++ b/KGySoft.Drawing.ImagingTools/View/Images.cs @@ -35,6 +35,9 @@ internal static class Images private static Bitmap? crop; private static Bitmap? highlightVisibleClip; private static Bitmap? magnifier; + private static Bitmap? magnifierPlus; + private static Bitmap? magnifierMinus; + private static Bitmap? magnifier1; private static Bitmap? save; private static Bitmap? open; private static Bitmap? clear; @@ -62,6 +65,9 @@ internal static class Images internal static Bitmap Crop => crop ??= GetResource(nameof(Crop)); internal static Bitmap HighlightVisibleClip => highlightVisibleClip ??= GetResource(nameof(HighlightVisibleClip)); internal static Bitmap Magnifier => magnifier ??= GetResource(nameof(Magnifier)); + internal static Bitmap MagnifierPlus => magnifierPlus ??= GetResource(nameof(MagnifierPlus)); + internal static Bitmap MagnifierMinus => magnifierMinus ??= GetResource(nameof(MagnifierMinus)); + internal static Bitmap Magnifier1 => magnifier1 ??= GetResource(nameof(Magnifier1)); internal static Bitmap Save => save ??= GetResource(nameof(Save)); internal static Bitmap Open => open ??= GetResource(nameof(Open)); internal static Bitmap Clear => clear ??= GetResource(nameof(Clear)); diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs index 835930c..5caed8f 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs @@ -16,7 +16,7 @@ partial class PreviewImageControl private void InitializeComponent() { this.scalingToolStrip1 = new KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStrip(); - this.btnAutoZoom = new System.Windows.Forms.ToolStripButton(); + this.btnZoom = new KGySoft.Drawing.ImagingTools.View.Controls.ZoomSplitButton(); this.btnAntiAlias = new System.Windows.Forms.ToolStripButton(); this.btnShowOriginal = new System.Windows.Forms.ToolStripButton(); this.ivPreview = new KGySoft.Drawing.ImagingTools.View.Controls.ImageViewer(); @@ -27,7 +27,7 @@ private void InitializeComponent() // this.scalingToolStrip1.Dock = System.Windows.Forms.DockStyle.Left; this.scalingToolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.btnAutoZoom, + this.btnZoom, this.btnAntiAlias, this.btnShowOriginal}); this.scalingToolStrip1.Location = new System.Drawing.Point(0, 0); @@ -38,11 +38,11 @@ private void InitializeComponent() // // btnAutoZoom // - this.btnAutoZoom.CheckOnClick = true; - this.btnAutoZoom.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.btnAutoZoom.ImageTransparentColor = System.Drawing.Color.Magenta; - this.btnAutoZoom.Name = "btnAutoZoom"; - this.btnAutoZoom.Size = new System.Drawing.Size(29, 4); + this.btnZoom.CheckOnClick = true; + this.btnZoom.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnZoom.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnZoom.Name = "btnZoom"; + this.btnZoom.Size = new System.Drawing.Size(29, 4); // // btnAntiAlias // @@ -84,7 +84,7 @@ private void InitializeComponent() #endregion private Controls.ScalingToolStrip scalingToolStrip1; - private System.Windows.Forms.ToolStripButton btnAutoZoom; + private Controls.ZoomSplitButton btnZoom; private System.Windows.Forms.ToolStripButton btnAntiAlias; private Controls.ImageViewer ivPreview; private System.Windows.Forms.ToolStripButton btnShowOriginal; diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs index 43fd0c2..a39e24d 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs @@ -87,7 +87,6 @@ protected override void Dispose(bool disposing) protected override void ApplyResources() { - btnAutoZoom.Image = Images.Magnifier; btnAntiAlias.Image = Images.SmoothZoom; btnShowOriginal.Image = Images.Compare; base.ApplyResources(); @@ -106,6 +105,12 @@ protected override void ApplyViewModel() private void InitCommandBindings() { + CommandBindings.Add(ivPreview.IncreaseZoom) + .AddSource(btnZoom.IncreaseZoomMenuItem, nameof(btnZoom.IncreaseZoomMenuItem.Click)); + CommandBindings.Add(ivPreview.DecreaseZoom) + .AddSource(btnZoom.DecreaseZoomMenuItem, nameof(btnZoom.DecreaseZoomMenuItem.Click)); + CommandBindings.Add(ivPreview.ResetZoom) + .AddSource(btnZoom.ResetZoomMenuItem, nameof(btnZoom.ResetZoomMenuItem.Click)); CommandBindings.Add(() => ViewModel!.ShowOriginal = true) .AddSource(btnShowOriginal, nameof(btnShowOriginal.MouseDown)); CommandBindings.Add(() => ViewModel!.ShowOriginal = false) @@ -121,7 +126,7 @@ private void InitPropertyBindings() CommandBindings.AddPropertyBinding(ViewModel!, nameof(ViewModel.ShowOriginalEnabled), nameof(ToolStripItem.Enabled), btnShowOriginal); // btnAutoZoom.Checked <-> VM.AutoZoom <-> ivPreview.AutoZoom - CommandBindings.AddTwoWayPropertyBinding(ViewModel!, nameof(ViewModel.AutoZoom), btnAutoZoom, nameof(btnAutoZoom.Checked)); + CommandBindings.AddTwoWayPropertyBinding(ViewModel!, nameof(ViewModel.AutoZoom), btnZoom, nameof(btnZoom.Checked)); CommandBindings.AddTwoWayPropertyBinding(ViewModel!, nameof(ViewModel.AutoZoom), ivPreview, nameof(ivPreview.AutoZoom)); // btnAntiAlias.Checked <-> VM.SmoothZooming -> ivPreview.SmoothZooming From 7ea2979e0d610656f59d010ecd88880bb15e6cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 16 May 2021 16:36:54 +0200 Subject: [PATCH 027/211] Fixing CheckableToolStripSplitButton scaling --- .../Controls/CheckableToolStripSplitButton.cs | 10 ++++ .../PreviewImageControl.Designer.cs | 55 ++++++++++--------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs index 9191233..96961f6 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs @@ -18,6 +18,7 @@ using System; using System.ComponentModel; +using System.Drawing; using System.Windows.Forms; #endregion @@ -79,6 +80,15 @@ protected override void OnButtonClick(EventArgs e) protected virtual void OnCheckedChanged(EventArgs e) => (Events[nameof(CheckedChanged)] as EventHandler)?.Invoke(this, e); + public override Size GetPreferredSize(Size constrainingSize) + { + if (Owner.Orientation == Orientation.Horizontal) + return base.GetPreferredSize(constrainingSize); + Size result = base.GetPreferredSize(constrainingSize); + + return new Size(result.Width, result.Height) + Owner.ScaleSize(new Size(2, 0)); + } + #endregion } } diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs index 5caed8f..883f783 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs @@ -15,34 +15,43 @@ partial class PreviewImageControl /// private void InitializeComponent() { - this.scalingToolStrip1 = new KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStrip(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PreviewImageControl)); + this.ivPreview = new KGySoft.Drawing.ImagingTools.View.Controls.ImageViewer(); + this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStrip(); this.btnZoom = new KGySoft.Drawing.ImagingTools.View.Controls.ZoomSplitButton(); this.btnAntiAlias = new System.Windows.Forms.ToolStripButton(); this.btnShowOriginal = new System.Windows.Forms.ToolStripButton(); - this.ivPreview = new KGySoft.Drawing.ImagingTools.View.Controls.ImageViewer(); - this.scalingToolStrip1.SuspendLayout(); + this.tsMenu.SuspendLayout(); this.SuspendLayout(); // - // scalingToolStrip1 + // ivPreview + // + this.ivPreview.Dock = System.Windows.Forms.DockStyle.Fill; + this.ivPreview.Location = new System.Drawing.Point(33, 0); + this.ivPreview.Name = "ivPreview"; + this.ivPreview.Size = new System.Drawing.Size(117, 150); + this.ivPreview.TabIndex = 1; + // + // tsMenu // - this.scalingToolStrip1.Dock = System.Windows.Forms.DockStyle.Left; - this.scalingToolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.tsMenu.Dock = System.Windows.Forms.DockStyle.Left; + this.tsMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.btnZoom, this.btnAntiAlias, this.btnShowOriginal}); - this.scalingToolStrip1.Location = new System.Drawing.Point(0, 0); - this.scalingToolStrip1.Name = "scalingToolStrip1"; - this.scalingToolStrip1.Size = new System.Drawing.Size(32, 150); - this.scalingToolStrip1.TabIndex = 0; - this.scalingToolStrip1.Text = "tsMenu"; + this.tsMenu.Location = new System.Drawing.Point(0, 0); + this.tsMenu.Name = "tsMenu"; + this.tsMenu.Size = new System.Drawing.Size(33, 150); + this.tsMenu.TabIndex = 0; + this.tsMenu.Text = "tsMenu"; // - // btnAutoZoom + // btnZoom // this.btnZoom.CheckOnClick = true; this.btnZoom.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; this.btnZoom.ImageTransparentColor = System.Drawing.Color.Magenta; this.btnZoom.Name = "btnZoom"; - this.btnZoom.Size = new System.Drawing.Size(29, 4); + this.btnZoom.Size = new System.Drawing.Size(30, 20); // // btnAntiAlias // @@ -50,32 +59,24 @@ private void InitializeComponent() this.btnAntiAlias.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; this.btnAntiAlias.ImageTransparentColor = System.Drawing.Color.Magenta; this.btnAntiAlias.Name = "btnAntiAlias"; - this.btnAntiAlias.Size = new System.Drawing.Size(29, 4); + this.btnAntiAlias.Size = new System.Drawing.Size(30, 4); // // btnShowOriginal // this.btnShowOriginal.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; this.btnShowOriginal.ImageTransparentColor = System.Drawing.Color.Magenta; this.btnShowOriginal.Name = "btnShowOriginal"; - this.btnShowOriginal.Size = new System.Drawing.Size(29, 4); - // - // ivPreview - // - this.ivPreview.Dock = System.Windows.Forms.DockStyle.Fill; - this.ivPreview.Location = new System.Drawing.Point(32, 0); - this.ivPreview.Name = "ivPreview"; - this.ivPreview.Size = new System.Drawing.Size(118, 150); - this.ivPreview.TabIndex = 1; + this.btnShowOriginal.Size = new System.Drawing.Size(30, 4); // // PreviewImageControl // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Controls.Add(this.ivPreview); - this.Controls.Add(this.scalingToolStrip1); + this.Controls.Add(this.tsMenu); this.Name = "PreviewImageControl"; - this.scalingToolStrip1.ResumeLayout(false); - this.scalingToolStrip1.PerformLayout(); + this.tsMenu.ResumeLayout(false); + this.tsMenu.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); @@ -83,7 +84,7 @@ private void InitializeComponent() #endregion - private Controls.ScalingToolStrip scalingToolStrip1; + private Controls.ScalingToolStrip tsMenu; private Controls.ZoomSplitButton btnZoom; private System.Windows.Forms.ToolStripButton btnAntiAlias; private Controls.ImageViewer ivPreview; From 8593776706fdcdfb44b296a1dbd0a504a5406e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 16 May 2021 18:06:35 +0200 Subject: [PATCH 028/211] Fixing the default scaling of the ToolStripSplitButton in .NET Framework when high DPI is enabled --- KGySoft.Drawing.ImagingTools/System/MathF.cs | 14 ++++++++++ .../Controls/CheckableToolStripSplitButton.cs | 28 ++++++++++++------- .../View/Controls/ScalingToolStrip.cs | 7 +++++ .../View/_Extensions/ControlExtensions.cs | 2 ++ .../_Extensions/Int32Extensions.cs | 10 +++++++ changelog.txt | 4 ++- 6 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/System/MathF.cs create mode 100644 KGySoft.Drawing.ImagingTools/_Extensions/Int32Extensions.cs diff --git a/KGySoft.Drawing.ImagingTools/System/MathF.cs b/KGySoft.Drawing.ImagingTools/System/MathF.cs new file mode 100644 index 0000000..1de5143 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/System/MathF.cs @@ -0,0 +1,14 @@ +#if NETFRAMEWORK +// ReSharper disable once CheckNamespace +namespace System +{ + internal static class MathF + { + #region Methods + + public static float Round(float x) => (float)Math.Round(x); + + #endregion + } +} +#endif \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs index 96961f6..02a78e3 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs @@ -28,8 +28,8 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls /// /// A whose button part can be checked. /// - // NOTE: Unlike ToolStripDropDownButton, ToolStripSplitButton is scaled well so no special handling is needed here - // The properly scaled arrow and the checked appearance is rendered by ScalingToolStripMenuRenderer + // NOTE: The properly scaled arrow and the checked appearance is rendered by ScalingToolStripMenuRenderer, while + // the drop-down button size is adjusted in ScalingToolStrip for all ToolStripSplitButtons internal class CheckableToolStripSplitButton : ToolStripSplitButton { #region Fields @@ -71,6 +71,21 @@ public event EventHandler CheckedChanged #region Methods + #region Public Methods + + public override Size GetPreferredSize(Size constrainingSize) + { + if (Owner.Orientation == Orientation.Horizontal) + return base.GetPreferredSize(constrainingSize); + Size result = base.GetPreferredSize(constrainingSize); + + return new Size(result.Width + Owner.ScaleWidth(2), result.Height); + } + + #endregion + + #region Protected Methods + protected override void OnButtonClick(EventArgs e) { if (CheckOnClick) @@ -80,14 +95,7 @@ protected override void OnButtonClick(EventArgs e) protected virtual void OnCheckedChanged(EventArgs e) => (Events[nameof(CheckedChanged)] as EventHandler)?.Invoke(this, e); - public override Size GetPreferredSize(Size constrainingSize) - { - if (Owner.Orientation == Orientation.Horizontal) - return base.GetPreferredSize(constrainingSize); - Size result = base.GetPreferredSize(constrainingSize); - - return new Size(result.Width, result.Height) + Owner.ScaleSize(new Size(2, 0)); - } + #endregion #endregion } diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs index 9d0608e..c26687b 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs @@ -206,6 +206,13 @@ protected override void WndProc(ref Message m) m.Result = (IntPtr)Constants.MA_ACTIVATE; } + protected override void OnItemAdded(ToolStripItemEventArgs e) + { + if (e.Item is ToolStripSplitButton btn) + btn.DropDownButtonWidth = this.ScaleWidth(11); + base.OnItemAdded(e); + } + #endregion } } diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs index d4155cd..64fccec 100644 --- a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs @@ -51,6 +51,8 @@ internal static PointF GetScale(this Control control) internal static Size ScaleSize(this Control control, Size size) => size.Scale(control.GetScale()); + internal static int ScaleWidth(this Control control, int width) => width.Scale(control.GetScale().X); + /// /// Applies fixed string resources (which do not change unless language is changed) to a control. /// diff --git a/KGySoft.Drawing.ImagingTools/_Extensions/Int32Extensions.cs b/KGySoft.Drawing.ImagingTools/_Extensions/Int32Extensions.cs new file mode 100644 index 0000000..6eee802 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/_Extensions/Int32Extensions.cs @@ -0,0 +1,10 @@ +using System; + +namespace KGySoft.Drawing.ImagingTools +{ + internal static class Int32Extensions + { + internal static int Scale(this int size, float scale) => + (int)MathF.Round(size * scale); + } +} diff --git a/changelog.txt b/changelog.txt index b3dbb7f..7c74070 100644 --- a/changelog.txt +++ b/changelog.txt @@ -13,8 +13,10 @@ ================================== + Targeting also .NET 5.0 + Now panning a zoomed image is possible also by clicking and dragging with the mouse (besides usual scrolling). ++ Zooming is now possible also with keyboard shortcuts, the Auto Zoom button has now a drop-down part for the + additional options. * Manual zooming is enabled even if Auto Zoom is on; this turns off Auto Zoom automatically. -* Smooth Zooming on/off affects also shrunk images (previously affected enlarged images only) +* Turning smoothing of zoomed images on/off affects also shrunk images (previously affected enlarged images only). - Fixing possible errors when closing forms while an async operation is still in progress. * API changes: * Members are annotated for using C# 8.0 nullable references From 0d5a9f5e5702449a6704b01c9409065bcbbbe599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 16 May 2021 18:23:05 +0200 Subject: [PATCH 029/211] Applying formatting --- .../_Extensions/Int32Extensions.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/_Extensions/Int32Extensions.cs b/KGySoft.Drawing.ImagingTools/_Extensions/Int32Extensions.cs index 6eee802..ddf8beb 100644 --- a/KGySoft.Drawing.ImagingTools/_Extensions/Int32Extensions.cs +++ b/KGySoft.Drawing.ImagingTools/_Extensions/Int32Extensions.cs @@ -1,10 +1,34 @@ -using System; +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: Int32Extensions.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; + +#endregion namespace KGySoft.Drawing.ImagingTools { internal static class Int32Extensions { + #region Methods + internal static int Scale(this int size, float scale) => (int)MathF.Round(size * scale); + + #endregion } -} +} \ No newline at end of file From 2a0d09d65023440c815623a0b0e84fba01d3a7b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 17 May 2021 21:09:00 +0200 Subject: [PATCH 030/211] Renaming CheckableToolStripSplitButton to Advanced* and adding auto select functionality --- ...ton.cs => AdvancedToolStripSplitButton.cs} | 37 ++++++++++++++++++- .../View/Controls/ScalingToolStrip.cs | 7 ++-- .../View/Controls/ZoomSplitButton.cs | 2 +- 3 files changed, 40 insertions(+), 6 deletions(-) rename KGySoft.Drawing.ImagingTools/View/Controls/{CheckableToolStripSplitButton.cs => AdvancedToolStripSplitButton.cs} (71%) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStripSplitButton.cs similarity index 71% rename from KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs rename to KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStripSplitButton.cs index 02a78e3..fb027d6 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/CheckableToolStripSplitButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStripSplitButton.cs @@ -1,7 +1,7 @@ #region Copyright /////////////////////////////////////////////////////////////////////////////// -// File: CheckableToolStripSplitButton.cs +// File: AdvancedToolStripSplitButton.cs /////////////////////////////////////////////////////////////////////////////// // Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved // @@ -30,11 +30,12 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls /// // NOTE: The properly scaled arrow and the checked appearance is rendered by ScalingToolStripMenuRenderer, while // the drop-down button size is adjusted in ScalingToolStrip for all ToolStripSplitButtons - internal class CheckableToolStripSplitButton : ToolStripSplitButton + internal class AdvancedToolStripSplitButton : ToolStripSplitButton { #region Fields private bool isChecked; + private bool autoChangeDefaultItem; #endregion @@ -57,6 +58,20 @@ public bool Checked } } + [DefaultValue(false)] + public bool AutoChangeDefaultItem + { + get => autoChangeDefaultItem; + set + { + if (value == autoChangeDefaultItem) + return; + autoChangeDefaultItem = value; + if (value && DropDownItems.Count > 0) + SetDefaultItem(DropDownItems[0]); + } + } + #endregion #region Events @@ -84,6 +99,17 @@ public override Size GetPreferredSize(Size constrainingSize) #endregion + #region Internal Methods + + internal void SetDefaultItem(ToolStripItem item) + { + DefaultItem = item; + Image = item.Image; + Text = item.Text; + } + + #endregion + #region Protected Methods protected override void OnButtonClick(EventArgs e) @@ -95,6 +121,13 @@ protected override void OnButtonClick(EventArgs e) protected virtual void OnCheckedChanged(EventArgs e) => (Events[nameof(CheckedChanged)] as EventHandler)?.Invoke(this, e); + protected override void OnDropDownItemClicked(ToolStripItemClickedEventArgs e) + { + base.OnDropDownItemClicked(e); + if (autoChangeDefaultItem && DefaultItem != e.ClickedItem) + SetDefaultItem(e.ClickedItem); + } + #endregion #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs index c26687b..6a45638 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs @@ -142,7 +142,7 @@ protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e) protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventArgs e) { base.OnRenderSplitButtonBackground(e); - if (e.Item is not CheckableToolStripSplitButton btn) + if (e.Item is not AdvancedToolStripSplitButton btn) return; // overriding background to behave the same way as ToolStripButton @@ -208,8 +208,9 @@ protected override void WndProc(ref Message m) protected override void OnItemAdded(ToolStripItemEventArgs e) { - if (e.Item is ToolStripSplitButton btn) - btn.DropDownButtonWidth = this.ScaleWidth(11); + if (e.Item is ToolStripSplitButton splitBtn) + splitBtn.DropDownButtonWidth = this.ScaleWidth(11); + base.OnItemAdded(e); } diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ZoomSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ZoomSplitButton.cs index e0d7c17..16fb5f0 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ZoomSplitButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ZoomSplitButton.cs @@ -23,7 +23,7 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls { - internal class ZoomSplitButton : CheckableToolStripSplitButton + internal class ZoomSplitButton : AdvancedToolStripSplitButton { #region Properties From 47f2c3b79787b4acbe81ea94a51a699a0612de91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 18 May 2021 14:33:01 +0200 Subject: [PATCH 031/211] Refactoring ToolTip resource application --- KGySoft.Drawing.ImagingTools/Res.cs | 6 ++- .../Forms/ImageVisualizerForm.Designer.cs | 39 +++++++++++++++--- .../View/Forms/ImageVisualizerForm.cs | 27 +++++++------ .../View/Forms/MvvmBaseForm.Designer.cs | 21 ++++++++++ .../View/Forms/MvvmBaseForm.cs | 11 +++-- .../View/_Extensions/ControlExtensions.cs | 40 ++++++++++++++++--- 6 files changed, 116 insertions(+), 28 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 3cf4bdb..13be0d7 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -231,7 +231,9 @@ internal static class Res #region General - internal static string Get(string id) => resourceManager.GetString(id, LanguageSettings.DisplayLanguage) ?? String.Format(CultureInfo.InvariantCulture, unavailableResource, id); + internal static string? GetStringOrNull(string id) => resourceManager.GetString(id, LanguageSettings.DisplayLanguage); + + internal static string Get(string id) => GetStringOrNull(id) ?? String.Format(CultureInfo.InvariantCulture, unavailableResource, id); internal static string Get(TEnum value) where TEnum : struct, Enum => Get($"{value.GetType().Name}.{Enum.ToString(value)}"); @@ -245,7 +247,7 @@ internal static void ApplyResources(object target, string name) foreach (PropertyInfo property in properties) { - string? value = resourceManager.GetString(name + "." + property.Name, LanguageSettings.DisplayLanguage); + string? value = GetStringOrNull(name + "." + property.Name); if (value == null) continue; Reflector.SetProperty(target, property, value); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs index d6833fb..932fb42 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs @@ -19,10 +19,10 @@ partial class ImageVisualizerForm private void InitializeComponent() { this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ImageVisualizerForm)); this.dlgOpen = new System.Windows.Forms.OpenFileDialog(); this.dlgSave = new System.Windows.Forms.SaveFileDialog(); this.timerPlayer = new System.Windows.Forms.Timer(this.components); - this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.imageViewer = new KGySoft.Drawing.ImagingTools.View.Controls.ImageViewer(); this.lblNotification = new KGySoft.Drawing.ImagingTools.View.Controls.NotificationLabel(); this.splitter = new System.Windows.Forms.Splitter(); @@ -55,8 +55,11 @@ private void InitializeComponent() this.btnCompound = new System.Windows.Forms.ToolStripButton(); this.btnPrev = new System.Windows.Forms.ToolStripButton(); this.btnNext = new System.Windows.Forms.ToolStripButton(); - this.btnConfiguration = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); + this.btnConfiguration = new KGySoft.Drawing.ImagingTools.View.Controls.AdvancedToolStripSplitButton(); this.txtInfo = new System.Windows.Forms.TextBox(); + this.miManageInstallations = new System.Windows.Forms.ToolStripMenuItem(); + this.miLanguageSettings = new System.Windows.Forms.ToolStripMenuItem(); this.tsMenu.SuspendLayout(); this.SuspendLayout(); // @@ -110,6 +113,7 @@ private void InitializeComponent() this.btnCompound, this.btnPrev, this.btnNext, + this.toolStripSeparator4, this.btnConfiguration}); this.tsMenu.Location = new System.Drawing.Point(0, 0); this.tsMenu.Name = "tsMenu"; @@ -122,7 +126,7 @@ private void InitializeComponent() this.btnZoom.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; this.btnZoom.ImageTransparentColor = System.Drawing.Color.Magenta; this.btnZoom.Name = "btnZoom"; - this.btnZoom.Size = new System.Drawing.Size(16, 22); + this.btnZoom.Size = new System.Drawing.Size(32, 22); // // btnAntiAlias // @@ -332,12 +336,21 @@ private void InitializeComponent() this.btnNext.Name = "btnNext"; this.btnNext.Size = new System.Drawing.Size(23, 22); // + // toolStripSeparator4 + // + this.toolStripSeparator4.Name = "toolStripSeparator4"; + this.toolStripSeparator4.Size = new System.Drawing.Size(6, 25); + // // btnConfiguration // + this.btnConfiguration.AutoChangeDefaultItem = true; this.btnConfiguration.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnConfiguration.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.miManageInstallations, + this.miLanguageSettings}); this.btnConfiguration.ImageTransparentColor = System.Drawing.Color.Magenta; this.btnConfiguration.Name = "btnConfiguration"; - this.btnConfiguration.Size = new System.Drawing.Size(23, 22); + this.btnConfiguration.Size = new System.Drawing.Size(16, 22); // // txtInfo // @@ -352,6 +365,18 @@ private void InitializeComponent() this.txtInfo.TabStop = false; this.txtInfo.WordWrap = false; // + // miManageInstallations + // + this.miManageInstallations.Name = "miManageInstallations"; + this.miManageInstallations.Size = new System.Drawing.Size(194, 22); + this.miManageInstallations.Text = "miManageInstallations"; + // + // miLanguageSettings + // + this.miLanguageSettings.Name = "miLanguageSettings"; + this.miLanguageSettings.Size = new System.Drawing.Size(194, 22); + this.miLanguageSettings.Text = "miLanguageSettings"; + // // ImageVisualizerForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -397,9 +422,8 @@ private void InitializeComponent() protected KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStrip tsMenu; protected System.Windows.Forms.TextBox txtInfo; private KGySoft.Drawing.ImagingTools.View.Controls.NotificationLabel lblNotification; - private ToolTip toolTip; private ToolStripSeparator toolStripSeparator2; - protected ToolStripButton btnConfiguration; + protected AdvancedToolStripSplitButton btnConfiguration; private ToolStripButton btnAntiAlias; private ToolStripMenuItem miCountColors; private ScalingToolStripDropDownButton btnEdit; @@ -412,5 +436,8 @@ private void InitializeComponent() private ToolStripMenuItem miBrightness; private ToolStripMenuItem miContrast; private ToolStripMenuItem miGamma; + private ToolStripSeparator toolStripSeparator4; + private ToolStripMenuItem miManageInstallations; + private ToolStripMenuItem miLanguageSettings; } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index ab98679..6c7429d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -18,7 +18,6 @@ using System; using System.Drawing; -using System.Globalization; using System.Windows.Forms; using KGySoft.ComponentModel; @@ -91,15 +90,17 @@ protected override void ApplyResources() // applying static resources base.ApplyResources(); Icon = Properties.Resources.ImagingTools; + btnAntiAlias.Image = Images.SmoothZoom; btnOpen.Image = Images.Open; btnSave.Image = Images.Save; btnClear.Image = Images.Clear; - btnColorSettings.Image = Images.Palette; btnPrev.Image = Images.Prev; btnNext.Image = Images.Next; - btnConfiguration.Image = Images.Settings; - btnAntiAlias.Image = Images.SmoothZoom; + btnColorSettings.Image = Images.Palette; btnEdit.Image = Images.Edit; + miManageInstallations.Image = Images.Settings; + miLanguageSettings.Image = Images.Language; + btnConfiguration.SetDefaultItem(miManageInstallations); miShowPalette.Image = Images.Palette; miBackColorDefault.Image = Images.Check; @@ -109,8 +110,6 @@ protected override void ApplyResources() miColorSpace.Image = Images.Quantize; miAdjustColors.Image = Images.Colors; - toolTip.SetToolTip(lblNotification, Res.Get($"{nameof(lblNotification)}.ToolTip")); - // base cannot handle these because components do not have names and dialogs are not even added to components field dlgOpen.Title = Res.Get($"{nameof(dlgOpen)}.{nameof(dlgOpen.Title)}"); dlgSave.Title = Res.Get($"{nameof(dlgSave)}.{nameof(dlgSave.Title)}"); @@ -237,6 +236,10 @@ private void InitCommandBindings() CommandBindings.Add(ViewModel.SetSmoothZoomingCommand, ViewModel.SetSmoothZoomingCommandState) .WithParameter(() => btnAntiAlias.Checked) .AddSource(btnAntiAlias, nameof(btnAntiAlias.CheckedChanged)); + CommandBindings.Add(ViewModel.PrevImageCommand, ViewModel.PrevImageCommandState) + .AddSource(btnPrev, nameof(btnPrev.Click)); + CommandBindings.Add(ViewModel.NextImageCommand, ViewModel.NextImageCommandState) + .AddSource(btnNext, nameof(btnNext.Click)); // File CommandBindings.Add(ViewModel.OpenFileCommand, ViewModel.OpenFileCommandState) @@ -272,18 +275,18 @@ private void InitCommandBindings() CommandBindings.Add(ViewModel.AdjustGammaCommand, ViewModel.EditBitmapCommandState) .AddSource(miGamma, nameof(miGamma.Click)); + // Configuration + CommandBindings.Add(ViewModel.ManageInstallationsCommand) + .AddSource(miManageInstallations, nameof(miManageInstallations.Click)); + CommandBindings.Add(ViewModel.SetLanguageCommand) + .AddSource(miLanguageSettings, nameof(miLanguageSettings.Click)); + // Compound controls CommandBindings.Add(ViewModel.SetCompoundViewCommand, ViewModel.SetCompoundViewCommandState) .WithParameter(() => btnCompound.Checked) .AddSource(btnCompound, nameof(btnCompound.CheckedChanged)); CommandBindings.Add(ViewModel.AdvanceAnimationCommand, ViewModel.AdvanceAnimationCommandState) .AddSource(timerPlayer, nameof(timerPlayer.Tick)); - CommandBindings.Add(ViewModel.PrevImageCommand, ViewModel.PrevImageCommandState) - .AddSource(btnPrev, nameof(btnPrev.Click)); - CommandBindings.Add(ViewModel.NextImageCommand, ViewModel.NextImageCommandState) - .AddSource(btnNext, nameof(btnNext.Click)); - CommandBindings.Add(ViewModel.ManageInstallationsCommand) - .AddSource(btnConfiguration, nameof(btnConfiguration.Click)); CommandBindings.Add(ViewModel.ViewImagePreviewSizeChangedCommand) .AddSource(imageViewer, nameof(imageViewer.SizeChanged)) .AddSource(imageViewer, nameof(imageViewer.ZoomChanged)); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs new file mode 100644 index 0000000..07c43c4 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs @@ -0,0 +1,21 @@ +namespace KGySoft.Drawing.ImagingTools.View.Forms +{ + partial class MvvmBaseForm + { + private System.Windows.Forms.ToolTip toolTip; + private System.ComponentModel.IContainer components; + + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.toolTip = new System.Windows.Forms.ToolTip(this.components); + this.SuspendLayout(); + // + // MvvmBaseForm + // + this.ClientSize = new System.Drawing.Size(284, 261); + this.Name = "MvvmBaseForm"; + this.ResumeLayout(false); + } + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index 72d28b9..e410b4c 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -26,7 +26,7 @@ namespace KGySoft.Drawing.ImagingTools.View.Forms { - internal class MvvmBaseForm : BaseForm, IView + internal partial class MvvmBaseForm : BaseForm, IView where TViewModel : IDisposable // BUG: Actually should be ViewModelBase but WinForms designer with derived forms dies from that { #region Fields @@ -59,6 +59,8 @@ internal class MvvmBaseForm : BaseForm, IView protected MvvmBaseForm(TViewModel viewModel) { + InitializeComponent(); + // occurs in design mode but DesignMode is false for grandchild forms if (viewModel == null!) return; @@ -79,7 +81,7 @@ protected MvvmBaseForm(TViewModel viewModel) #region Private Constructors - private MvvmBaseForm() + private MvvmBaseForm() : this(default!) { // this ctor is just for the designer } @@ -104,7 +106,7 @@ protected override void OnLoad(EventArgs e) ApplyViewModel(); } - protected virtual void ApplyResources() => this.ApplyStaticStringResources(); + protected virtual void ApplyResources() => this.ApplyStaticStringResources(toolTip); protected virtual void ApplyViewModel() => VM.ViewLoaded(); @@ -118,7 +120,10 @@ protected override void OnFormClosing(FormClosingEventArgs e) protected override void Dispose(bool disposing) { if (disposing) + { + components?.Dispose(); CommandBindings.Dispose(); + } base.Dispose(disposing); } diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs index 64fccec..198907a 100644 --- a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs @@ -19,7 +19,7 @@ using System; using System.Drawing; using System.Windows.Forms; - +using KGySoft.Drawing.ImagingTools.View.Controls; using KGySoft.Reflection; #endregion @@ -28,6 +28,12 @@ namespace KGySoft.Drawing.ImagingTools.View { internal static class ControlExtensions { + #region Constants + + private const string toolTipPropertyName = "ToolTipText"; + + #endregion + #region Methods /// @@ -56,8 +62,20 @@ internal static PointF GetScale(this Control control) /// /// Applies fixed string resources (which do not change unless language is changed) to a control. /// - internal static void ApplyStaticStringResources(this Control control) + internal static void ApplyStaticStringResources(this Control control, ToolTip? toolTip = null) { + #region Local Methods + + static void ApplyToolTip(Control control, string name, ToolTip toolTip) + { + string? value = Res.GetStringOrNull(name + "." + toolTipPropertyName); + toolTip.SetToolTip(control, value); + + // CheckGroupBox: applying the ToolTip to the contained CheckBox + if (control is CheckGroupBox checkGroupBox) + toolTip.SetToolTip(checkGroupBox.CheckBox, value); + } + static void ApplyToolStripResources(ToolStripItemCollection items) { foreach (ToolStripItem item in items) @@ -71,6 +89,8 @@ static void ApplyToolStripResources(ToolStripItemCollection items) } } + #endregion + string name = control.Name; if (String.IsNullOrEmpty(name)) name = control.GetType().Name; @@ -78,15 +98,25 @@ static void ApplyToolStripResources(ToolStripItemCollection items) // to self Res.ApplyResources(control, name); - // to children - foreach (Control child in control.Controls) child.ApplyStaticStringResources(); + // applying tool tip + if (toolTip != null) + ApplyToolTip(control, name, toolTip); - // to non-control sub-components + // to children switch (control) { case ToolStrip toolStrip: ApplyToolStripResources(toolStrip.Items); break; + + // preventing recursion + case CheckGroupBox: + break; + + default: + foreach (Control child in control.Controls) + child.ApplyStaticStringResources(toolTip); + break; } } From aae1373855fb04ef94c753fe232a08159ab2a00f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 19 May 2021 12:11:29 +0200 Subject: [PATCH 032/211] Allowing custom localization --- .../View/Controls/CheckGroupBox.cs | 29 ++++++++++++++-- .../View/Forms/MvvmBaseForm.cs | 2 +- .../DithererStrengthEditorControl.cs | 2 +- .../QuantizerThresholdEditorControl.cs | 2 +- .../View/_Extensions/ControlExtensions.cs | 27 ++++++++------- .../View/_Interfaces/ICustomLocalizable.cs | 33 +++++++++++++++++++ 6 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/View/_Interfaces/ICustomLocalizable.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs index eb52441..c81e231 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs @@ -28,7 +28,7 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls { - internal partial class CheckGroupBox : GroupBox + internal partial class CheckGroupBox : GroupBox, ICustomLocalizable { #region Events @@ -86,7 +86,7 @@ public CheckGroupBox() // Left should be 10 at 100% but only 8 at 175%, etc. checkBox.Left = Math.Max(1, 13 - (int)(this.GetScale().X * Padding.Left)); - // Vista or later: using System FlayStyle so animation is enabled with theming and while text is not misplaced with classic themes + // Vista or later: using System FlayStyle so animation is enabled with theming and text is not misplaced with classic themes bool visualStylesEnabled = Application.RenderWithVisualStyles; checkBox.FlatStyle = OSUtils.IsVistaOrLater ? FlatStyle.System // Windows XP: Using standard style with themes so CheckBox color can be set correctly, and using System with classic theme for good placement @@ -159,6 +159,31 @@ private void CheckBox_CheckedChanged(object? sender, EventArgs e) #endregion + #region Explicitly Implemented Interface Methods + + void ICustomLocalizable.ApplyStringResources(ToolTip? toolTip) + { + string? name = Name; + if (String.IsNullOrEmpty(name)) + return; + + // Self properties + Res.ApplyStringResources(this, name); + + // tool tip: forwarding to the check box + if (toolTip != null) + { + string? value = Res.GetStringOrNull(name + "." + ControlExtensions.ToolTipPropertyName); + toolTip.SetToolTip(checkBox, value); + } + + // children: only contentPanel controls so checkBox is skipped (otherwise, could be overwritten by checkbox.Name) + foreach (Control child in contentPanel.Controls) + child.ApplyStringResources(toolTip); + } + + #endregion + #endregion } } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index e410b4c..2b8f1a6 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -106,7 +106,7 @@ protected override void OnLoad(EventArgs e) ApplyViewModel(); } - protected virtual void ApplyResources() => this.ApplyStaticStringResources(toolTip); + protected virtual void ApplyResources() => this.ApplyStringResources(toolTip); protected virtual void ApplyViewModel() => VM.ViewLoaded(); diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs index f617fca..9c8d5d7 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs @@ -52,7 +52,7 @@ internal DithererStrengthEditorControl(IWindowsFormsEditorService editorService, trackBar.ValueChanged += TrackBar_ValueChanged; okCancelButtons.CancelButton.Click += CancelButton_Click; okCancelButtons.OKButton.Click += OKButton_Click; - okCancelButtons.ApplyStaticStringResources(); + okCancelButtons.ApplyStringResources(); originalValue = value; trackBar.Value = value >= 0f && value <= 1f ? (int)(value * 100) : 0; diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs index ee2b0f8..6300631 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs @@ -52,7 +52,7 @@ internal QuantizerThresholdEditorControl(IWindowsFormsEditorService editorServic trackBar.ValueChanged += TrackBar_ValueChanged; okCancelButtons.CancelButton.Click += CancelButton_Click; okCancelButtons.OKButton.Click += OKButton_Click; - okCancelButtons.ApplyStaticStringResources(); + okCancelButtons.ApplyStringResources(); trackBar.Value = originalValue = value; } diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs index 198907a..7c43558 100644 --- a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs @@ -30,7 +30,7 @@ internal static class ControlExtensions { #region Constants - private const string toolTipPropertyName = "ToolTipText"; + internal const string ToolTipPropertyName = "ToolTipText"; #endregion @@ -62,18 +62,14 @@ internal static PointF GetScale(this Control control) /// /// Applies fixed string resources (which do not change unless language is changed) to a control. /// - internal static void ApplyStaticStringResources(this Control control, ToolTip? toolTip = null) + internal static void ApplyStringResources(this Control control, ToolTip? toolTip = null) { #region Local Methods static void ApplyToolTip(Control control, string name, ToolTip toolTip) { - string? value = Res.GetStringOrNull(name + "." + toolTipPropertyName); + string? value = Res.GetStringOrNull(name + "." + ToolTipPropertyName); toolTip.SetToolTip(control, value); - - // CheckGroupBox: applying the ToolTip to the contained CheckBox - if (control is CheckGroupBox checkGroupBox) - toolTip.SetToolTip(checkGroupBox.CheckBox, value); } static void ApplyToolStripResources(ToolStripItemCollection items) @@ -81,7 +77,7 @@ static void ApplyToolStripResources(ToolStripItemCollection items) foreach (ToolStripItem item in items) { // to self - Res.ApplyResources(item, item.Name); + Res.ApplyStringResources(item, item.Name); // to children if (item is ToolStripDropDownItem dropDownItem) @@ -95,8 +91,15 @@ static void ApplyToolStripResources(ToolStripItemCollection items) if (String.IsNullOrEmpty(name)) name = control.GetType().Name; + // custom localization + if (control is ICustomLocalizable customLocalizable) + { + customLocalizable.ApplyStringResources(toolTip); + return; + } + // to self - Res.ApplyResources(control, name); + Res.ApplyStringResources(control, name); // applying tool tip if (toolTip != null) @@ -109,13 +112,9 @@ static void ApplyToolStripResources(ToolStripItemCollection items) ApplyToolStripResources(toolStrip.Items); break; - // preventing recursion - case CheckGroupBox: - break; - default: foreach (Control child in control.Controls) - child.ApplyStaticStringResources(toolTip); + child.ApplyStringResources(toolTip); break; } } diff --git a/KGySoft.Drawing.ImagingTools/View/_Interfaces/ICustomLocalizable.cs b/KGySoft.Drawing.ImagingTools/View/_Interfaces/ICustomLocalizable.cs new file mode 100644 index 0000000..91f71fd --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/_Interfaces/ICustomLocalizable.cs @@ -0,0 +1,33 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: ICustomLocalizable.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System.Windows.Forms; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View +{ + internal interface ICustomLocalizable + { + #region Methods + + void ApplyStringResources(ToolTip? toolTip); + + #endregion + } +} \ No newline at end of file From 8fcc1bb730e10f766dfb6aeac81506fcd61ec11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 19 May 2021 15:48:00 +0200 Subject: [PATCH 033/211] Adding language settings --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 38 +++- .../KGySoft.Drawing.ImagingTools.csproj | 25 ++- .../Properties/Resources.Designer.cs | 176 ++++++++++++---- .../Properties/Resources.resx | 3 + .../Properties/Settings.Designer.cs | 62 ++++++ .../Properties/Settings.settings | 15 ++ KGySoft.Drawing.ImagingTools/Res.cs | 103 ++++++++- .../Resources/Language.ico | Bin 0 -> 21169 bytes .../View/Forms/ImageVisualizerForm.cs | 6 +- .../Forms/LanguageSettingsForm.Designer.cs | 178 ++++++++++++++++ .../View/Forms/LanguageSettingsForm.cs | 131 ++++++++++++ .../Forms/ManageInstallationsForm.Designer.cs | 24 +-- .../View/Forms/ManageInstallationsForm.cs | 10 +- .../View/Forms/MvvmBaseForm.cs | 16 +- KGySoft.Drawing.ImagingTools/View/Images.cs | 2 + .../View/UserControls/OkCancelApplyButtons.cs | 58 ++++++ .../UserControls/OkCancelButtons.Designer.cs | 3 +- .../View/ViewFactory.cs | 44 ++-- .../ViewModel/ImageVisualizerViewModel.cs | 7 + .../ViewModel/LanguageSettingsViewModel.cs | 195 ++++++++++++++++++ .../ViewModel/ViewModelFactory.cs | 23 ++- changelog.txt | 2 + 22 files changed, 1017 insertions(+), 104 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/Properties/Settings.Designer.cs create mode 100644 KGySoft.Drawing.ImagingTools/Properties/Settings.settings create mode 100644 KGySoft.Drawing.ImagingTools/Resources/Language.ico create mode 100644 KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs create mode 100644 KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs create mode 100644 KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelApplyButtons.cs create mode 100644 KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 68b75c7..4c824b5 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -378,6 +378,9 @@ Either select at least '{1}' or reduce the number of colors to {2}. Value must be greater than {0} + + Failed to save settings: {0} + Could not create directory {0}: {1} @@ -565,9 +568,15 @@ Dithering may help to preserve more details. Next Image (Shift+Right) + Application Settings + + Manage Debugger Visualizer Installations... - + + Language Settings... + + Click to hide @@ -738,4 +747,31 @@ Dithering may help to preserve more details. Show Original + + Language Settings + + + Allow using resource files + + + When checked, allows using resources from .resx files directly + + + Use Operating System Language + + + When checked, uses the language of the operating system. The possibly non-existing resources will be automatically generated. + + + Show Languages Only With Existing Resources + + + When checked, enlists languages only with existing resource files. Uncheck to allow generating resources for any language. + + + Display Language + + + Edit Resources... + \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj index c000d5c..dd2e8ad 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj @@ -38,19 +38,38 @@ PublicResXFileCodeGenerator + True + True + Resources.resx - + + True + True + Settings.settings + + Component + + UserControl + KGySoft.Drawing.ImagingTools.Messages.resources - - AdjustColorsFormBase.resx + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + diff --git a/KGySoft.Drawing.ImagingTools/Properties/Resources.Designer.cs b/KGySoft.Drawing.ImagingTools/Properties/Resources.Designer.cs index 4acbb77..e39ef19 100644 --- a/KGySoft.Drawing.ImagingTools/Properties/Resources.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/Properties/Resources.Designer.cs @@ -19,10 +19,10 @@ namespace KGySoft.Drawing.ImagingTools.Properties { // 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", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { + public class Resources { private static global::System.Resources.ResourceManager resourceMan; @@ -36,7 +36,7 @@ internal Resources() { /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("KGySoft.Drawing.ImagingTools.Properties.Resources", typeof(Resources).Assembly); @@ -51,7 +51,7 @@ internal Resources() { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +63,7 @@ internal Resources() { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Animation { + public static System.Drawing.Icon Animation { get { object obj = ResourceManager.GetObject("Animation", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -73,7 +73,7 @@ internal static System.Drawing.Icon Animation { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Check { + public static System.Drawing.Icon Check { get { object obj = ResourceManager.GetObject("Check", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -83,41 +83,37 @@ internal static System.Drawing.Icon Check { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Clear { + public static System.Drawing.Icon Clear { get { object obj = ResourceManager.GetObject("Clear", resourceCulture); return ((System.Drawing.Icon)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Colors - { - get - { + public static System.Drawing.Icon Colors { + get { object obj = ResourceManager.GetObject("Colors", resourceCulture); return ((System.Drawing.Icon)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Compare - { - get - { + public static System.Drawing.Icon Compare { + get { object obj = ResourceManager.GetObject("Compare", resourceCulture); return ((System.Drawing.Icon)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Crop { + public static System.Drawing.Icon Crop { get { object obj = ResourceManager.GetObject("Crop", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -127,7 +123,37 @@ internal static System.Drawing.Icon Crop { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon HighlightVisibleClip { + public static System.Drawing.Icon Edit { + get { + object obj = ResourceManager.GetObject("Edit", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon HandGrab { + get { + object obj = ResourceManager.GetObject("HandGrab", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon HandOpen { + get { + object obj = ResourceManager.GetObject("HandOpen", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon HighlightVisibleClip { get { object obj = ResourceManager.GetObject("HighlightVisibleClip", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -137,7 +163,7 @@ internal static System.Drawing.Icon HighlightVisibleClip { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon ImagingTools { + public static System.Drawing.Icon ImagingTools { get { object obj = ResourceManager.GetObject("ImagingTools", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -147,7 +173,17 @@ internal static System.Drawing.Icon ImagingTools { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Magnifier { + public static System.Drawing.Icon Language { + get { + object obj = ResourceManager.GetObject("Language", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon Magnifier { get { object obj = ResourceManager.GetObject("Magnifier", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -157,7 +193,37 @@ internal static System.Drawing.Icon Magnifier { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon MultiPage { + public static System.Drawing.Icon Magnifier1 { + get { + object obj = ResourceManager.GetObject("Magnifier1", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon MagnifierMinus { + get { + object obj = ResourceManager.GetObject("MagnifierMinus", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon MagnifierPlus { + get { + object obj = ResourceManager.GetObject("MagnifierPlus", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon MultiPage { get { object obj = ResourceManager.GetObject("MultiPage", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -167,7 +233,7 @@ internal static System.Drawing.Icon MultiPage { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon MultiSize { + public static System.Drawing.Icon MultiSize { get { object obj = ResourceManager.GetObject("MultiSize", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -177,7 +243,7 @@ internal static System.Drawing.Icon MultiSize { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Next { + public static System.Drawing.Icon Next { get { object obj = ResourceManager.GetObject("Next", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -187,7 +253,7 @@ internal static System.Drawing.Icon Next { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Open { + public static System.Drawing.Icon Open { get { object obj = ResourceManager.GetObject("Open", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -197,7 +263,7 @@ internal static System.Drawing.Icon Open { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Palette { + public static System.Drawing.Icon Palette { get { object obj = ResourceManager.GetObject("Palette", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -207,29 +273,57 @@ internal static System.Drawing.Icon Palette { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Prev { + public static System.Drawing.Icon Prev { get { object obj = ResourceManager.GetObject("Prev", resourceCulture); return ((System.Drawing.Icon)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Resize - { - get - { + public static System.Drawing.Icon Quantize { + get { + object obj = ResourceManager.GetObject("Quantize", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon Resize { + get { object obj = ResourceManager.GetObject("Resize", resourceCulture); return ((System.Drawing.Icon)(obj)); } } - + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon RotateLeft { + get { + object obj = ResourceManager.GetObject("RotateLeft", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Save { + public static System.Drawing.Icon RotateRight { + get { + object obj = ResourceManager.GetObject("RotateRight", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon Save { get { object obj = ResourceManager.GetObject("Save", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -239,21 +333,19 @@ internal static System.Drawing.Icon Save { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Settings { + public static System.Drawing.Icon Settings { get { object obj = ResourceManager.GetObject("Settings", resourceCulture); return ((System.Drawing.Icon)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - internal static System.Drawing.Icon Quantize - { - get - { - object obj = ResourceManager.GetObject("Quantize", resourceCulture); + public static System.Drawing.Icon SmoothZoom { + get { + object obj = ResourceManager.GetObject("SmoothZoom", resourceCulture); return ((System.Drawing.Icon)(obj)); } } diff --git a/KGySoft.Drawing.ImagingTools/Properties/Resources.resx b/KGySoft.Drawing.ImagingTools/Properties/Resources.resx index e7761b7..7ca0005 100644 --- a/KGySoft.Drawing.ImagingTools/Properties/Resources.resx +++ b/KGySoft.Drawing.ImagingTools/Properties/Resources.resx @@ -151,6 +151,9 @@ ..\Resources\ImagingTools.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\Language.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\Magnifier.ico;System.Drawing.Icon, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/KGySoft.Drawing.ImagingTools/Properties/Settings.Designer.cs b/KGySoft.Drawing.ImagingTools/Properties/Settings.Designer.cs new file mode 100644 index 0000000..6bfee6b --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/Properties/Settings.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// 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 KGySoft.Drawing.ImagingTools.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool AllowResXResources { + get { + return ((bool)(this["AllowResXResources"])); + } + set { + this["AllowResXResources"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool UseOSLanguage { + get { + return ((bool)(this["UseOSLanguage"])); + } + set { + this["UseOSLanguage"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("(Default)")] + public global::System.Globalization.CultureInfo DisplayLanguage { + get { + return ((global::System.Globalization.CultureInfo)(this["DisplayLanguage"])); + } + set { + this["DisplayLanguage"] = value; + } + } + } +} diff --git a/KGySoft.Drawing.ImagingTools/Properties/Settings.settings b/KGySoft.Drawing.ImagingTools/Properties/Settings.settings new file mode 100644 index 0000000..7e9b5f1 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/Properties/Settings.settings @@ -0,0 +1,15 @@ + + + + + + False + + + False + + + (Default) + + + \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 13be0d7..4d04537 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -17,15 +17,19 @@ #region Usings using System; +using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; using System.Globalization; +using System.IO; using System.Linq; using System.Reflection; +using System.Resources; using KGySoft.Collections; using KGySoft.CoreLibraries; +using KGySoft.Drawing.ImagingTools.Properties; using KGySoft.Reflection; using KGySoft.Resources; @@ -50,14 +54,25 @@ internal static class Res UseLanguageSettings = true, }; - // Note: No need to use ThreadSafeCacheFactory here because used only from the UI thread + // Note: No need to use ThreadSafeCacheFactory here because used only from the UI thread when applying resources // ReSharper disable once CollectionNeverUpdated.Local - private static readonly Cache localizablePropertiesCache = new Cache(GetLocalizableProperties); + private static readonly Cache localizableStringPropertiesCache = new Cache(GetLocalizableStringProperties); + + private static StringKeyedDictionary? culturesCache; #endregion #region Properties + #region Internal Properties + + #region General + + internal static CultureInfo OSLanguage { get; } + internal static CultureInfo DefaultLanguage { get; } + + #endregion + #region Title Captions /// No Image @@ -225,23 +240,85 @@ internal static class Res #endregion + #region Private Properties + + private static StringKeyedDictionary CulturesCache + => culturesCache ??= CultureInfo.GetCultures(CultureTypes.AllCultures).ToStringKeyedDictionary(ci => ci.Name); + + #endregion + + #endregion + + #region Constructors + + static Res() + { + OSLanguage = LanguageSettings.DisplayLanguage.GetClosestNeutralCulture(); + DefaultLanguage = (Attribute.GetCustomAttribute(typeof(Res).Assembly, typeof(NeutralResourcesLanguageAttribute)) is NeutralResourcesLanguageAttribute attr + ? CultureInfo.GetCultureInfo(attr.CultureName) + : CultureInfo.InvariantCulture).GetClosestNeutralCulture(); + DrawingModule.Initialize(); + + bool allowResXResources = Settings.Default.AllowResXResources; + LanguageSettings.DisplayLanguage = allowResXResources + ? Settings.Default.UseOSLanguage ? OSLanguage : Settings.Default.DisplayLanguage.GetClosestNeutralCulture() + : DefaultLanguage; + LanguageSettings.DynamicResourceManagersSource = allowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; + } + + #endregion + #region Methods #region Internal Methods #region General + /// + /// Just an empty method to be able to trigger the static constructor without running any code other than field initializations. + /// + internal static void EnsureInitialized() + { + } + + internal static IList GetAvailableLanguages() + { + string dir = resourceManager.ResXResourcesDir; + try + { + var result = new List { DefaultLanguage }; + if (!Directory.Exists(dir)) + return result; + + string baseName = resourceManager.BaseName; + int startIndex = dir.Length + baseName.Length + 2; + string[] files = Directory.GetFiles(dir, $"{baseName}.*.resx", SearchOption.TopDirectoryOnly); + foreach (string file in files) + { + StringSegment resName = file.AsSegment(startIndex, file.Length - startIndex - 5); + if (CulturesCache.TryGetValue(resName, out CultureInfo? ci) && !ci.In(CultureInfo.InvariantCulture, DefaultLanguage)) + result.Add(ci); + } + + return result; + } + catch (Exception e) when (!e.IsCritical()) + { + return new[] { DefaultLanguage }; + } + } + internal static string? GetStringOrNull(string id) => resourceManager.GetString(id, LanguageSettings.DisplayLanguage); internal static string Get(string id) => GetStringOrNull(id) ?? String.Format(CultureInfo.InvariantCulture, unavailableResource, id); internal static string Get(TEnum value) where TEnum : struct, Enum => Get($"{value.GetType().Name}.{Enum.ToString(value)}"); - internal static void ApplyResources(object target, string name) + internal static void ApplyStringResources(object target, string name) { // Unlike ComponentResourceManager we don't go by ResourceSet because that would kill resource fallback traversal // so we go by localizable properties - PropertyInfo[]? properties = localizablePropertiesCache[target.GetType()]; + PropertyInfo[]? properties = localizableStringPropertiesCache[target.GetType()]; if (properties == null) return; @@ -435,6 +512,9 @@ internal static string InfoColor(int argb, string knownColors, string systemColo /// Value must be greater than {0} internal static string ErrorMessageValueMustBeGreaterThan(T value) where T : struct => Get("ErrorMessage_ValueMustBeGreaterThanFormat", value); + /// Failed to save settings: {0} + internal static string ErrorMessageFailedToSaveSettings(string message) => Get("ErrorMessage_FailedToSaveSettingsFormat", message); + /// Could not create directory {0}: {1} /// /// The debugger visualizer may will not work for .NET Core projects. @@ -543,15 +623,26 @@ private static string SafeFormat(string format, object?[] args) } } - private static PropertyInfo[]? GetLocalizableProperties(Type type) + private static PropertyInfo[]? GetLocalizableStringProperties(Type type) { // Getting string properties only. The resource manager in this class works in safe mode anyway. var result = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.PropertyType == typeof(string) - && Attribute.GetCustomAttribute(p, typeof(LocalizableAttribute)) is LocalizableAttribute la && la.IsLocalizable).ToArray(); + && Attribute.GetCustomAttribute(p, typeof(LocalizableAttribute)) is LocalizableAttribute la && la.IsLocalizable).ToArray(); return result.Length == 0 ? null : result; } + private static CultureInfo GetClosestNeutralCulture(this CultureInfo culture) + { + if (CultureInfo.InvariantCulture.Equals(culture)) + return culture; + + while (!culture.IsNeutralCulture) + culture = culture.Parent; + + return culture; + } + #endregion #endregion diff --git a/KGySoft.Drawing.ImagingTools/Resources/Language.ico b/KGySoft.Drawing.ImagingTools/Resources/Language.ico new file mode 100644 index 0000000000000000000000000000000000000000..47394ce6b5b271239ed92e4db182f25a9c69a1c7 GIT binary patch literal 21169 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 + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.okCancelApplyButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelApplyButtons(); + this.gbAllowResxResources = new KGySoft.Drawing.ImagingTools.View.Controls.CheckGroupBox(); + this.gbDisplayLanguage = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.btnEditResources = new System.Windows.Forms.Button(); + this.cmbLanguages = new System.Windows.Forms.ComboBox(); + this.chbExistingResourcesOnly = new System.Windows.Forms.CheckBox(); + this.chbUseOSLanguage = new System.Windows.Forms.CheckBox(); + this.toolTip = new System.Windows.Forms.ToolTip(this.components); + this.gbAllowResxResources.SuspendLayout(); + this.gbDisplayLanguage.SuspendLayout(); + this.tableLayoutPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // okCancelApplyButtons + // + this.okCancelApplyButtons.Dock = System.Windows.Forms.DockStyle.Bottom; + this.okCancelApplyButtons.Location = new System.Drawing.Point(3, 108); + this.okCancelApplyButtons.Name = "okCancelApplyButtons"; + this.okCancelApplyButtons.Size = new System.Drawing.Size(298, 40); + this.okCancelApplyButtons.TabIndex = 1; + // + // gbAllowResxResources + // + this.gbAllowResxResources.Controls.Add(this.gbDisplayLanguage); + this.gbAllowResxResources.Controls.Add(this.chbExistingResourcesOnly); + this.gbAllowResxResources.Controls.Add(this.chbUseOSLanguage); + this.gbAllowResxResources.Dock = System.Windows.Forms.DockStyle.Fill; + this.gbAllowResxResources.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.gbAllowResxResources.Location = new System.Drawing.Point(3, 3); + this.gbAllowResxResources.Name = "gbAllowResxResources"; + this.gbAllowResxResources.Padding = new System.Windows.Forms.Padding(5); + this.gbAllowResxResources.Size = new System.Drawing.Size(298, 105); + this.gbAllowResxResources.TabIndex = 0; + this.gbAllowResxResources.TabStop = false; + this.gbAllowResxResources.Text = "gbAllowResxResources"; + // + // gbDisplayLanguage + // + this.gbDisplayLanguage.Controls.Add(this.tableLayoutPanel1); + this.gbDisplayLanguage.Dock = System.Windows.Forms.DockStyle.Fill; + this.gbDisplayLanguage.Location = new System.Drawing.Point(5, 54); + this.gbDisplayLanguage.Name = "gbDisplayLanguage"; + this.gbDisplayLanguage.Size = new System.Drawing.Size(288, 46); + this.gbDisplayLanguage.TabIndex = 2; + this.gbDisplayLanguage.TabStop = false; + this.gbDisplayLanguage.Text = "gbDisplayLanguage"; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.Controls.Add(this.btnEditResources, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.cmbLanguages, 0, 0); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 16); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 1; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(282, 27); + this.tableLayoutPanel1.TabIndex = 0; + // + // btnEditResources + // + this.btnEditResources.AutoSize = true; + this.btnEditResources.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.btnEditResources.Location = new System.Drawing.Point(174, 2); + this.btnEditResources.Margin = new System.Windows.Forms.Padding(3, 2, 3, 3); + this.btnEditResources.MinimumSize = new System.Drawing.Size(0, 23); + this.btnEditResources.Name = "btnEditResources"; + this.btnEditResources.Size = new System.Drawing.Size(105, 23); + this.btnEditResources.TabIndex = 1; + this.btnEditResources.Text = "btnEditResources"; + this.btnEditResources.UseVisualStyleBackColor = true; + // + // cmbLanguages + // + this.cmbLanguages.Dock = System.Windows.Forms.DockStyle.Top; + this.cmbLanguages.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbLanguages.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.cmbLanguages.FormattingEnabled = true; + this.cmbLanguages.Location = new System.Drawing.Point(3, 3); + this.cmbLanguages.Name = "cmbLanguages"; + this.cmbLanguages.Size = new System.Drawing.Size(165, 21); + this.cmbLanguages.TabIndex = 0; + // + // chbExistingResourcesOnly + // + this.chbExistingResourcesOnly.AutoSize = true; + this.chbExistingResourcesOnly.Dock = System.Windows.Forms.DockStyle.Top; + this.chbExistingResourcesOnly.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.chbExistingResourcesOnly.Location = new System.Drawing.Point(5, 36); + this.chbExistingResourcesOnly.Name = "chbExistingResourcesOnly"; + this.chbExistingResourcesOnly.Size = new System.Drawing.Size(288, 18); + this.chbExistingResourcesOnly.TabIndex = 1; + this.chbExistingResourcesOnly.Text = "chbExistingResourcesOnly"; + this.chbExistingResourcesOnly.UseVisualStyleBackColor = true; + // + // chbUseOSLanguage + // + this.chbUseOSLanguage.AutoSize = true; + this.chbUseOSLanguage.Dock = System.Windows.Forms.DockStyle.Top; + this.chbUseOSLanguage.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.chbUseOSLanguage.Location = new System.Drawing.Point(5, 18); + this.chbUseOSLanguage.Name = "chbUseOSLanguage"; + this.chbUseOSLanguage.Size = new System.Drawing.Size(288, 18); + this.chbUseOSLanguage.TabIndex = 0; + this.chbUseOSLanguage.Text = "chbUseOSLanguage"; + this.chbUseOSLanguage.UseVisualStyleBackColor = true; + // + // LanguageSettingsForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(304, 151); + this.Controls.Add(this.gbAllowResxResources); + this.Controls.Add(this.okCancelApplyButtons); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "LanguageSettingsForm"; + this.Padding = new System.Windows.Forms.Padding(3); + this.Text = "LanguageSettingsForm"; + this.gbAllowResxResources.ResumeLayout(false); + this.gbAllowResxResources.PerformLayout(); + this.gbDisplayLanguage.ResumeLayout(false); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private UserControls.OkCancelApplyButtons okCancelApplyButtons; + private KGySoft.Drawing.ImagingTools.View.Controls.CheckGroupBox gbAllowResxResources; + private System.Windows.Forms.CheckBox chbExistingResourcesOnly; + private System.Windows.Forms.ToolTip toolTip; + private System.Windows.Forms.GroupBox gbDisplayLanguage; + private System.Windows.Forms.CheckBox chbUseOSLanguage; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Button btnEditResources; + private System.Windows.Forms.ComboBox cmbLanguages; + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs new file mode 100644 index 0000000..fbb2621 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs @@ -0,0 +1,131 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: LanguageSettingsForm.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System.Globalization; +using System.Windows.Forms; + +using KGySoft.ComponentModel; +using KGySoft.Drawing.ImagingTools.ViewModel; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View.Forms +{ + internal partial class LanguageSettingsForm : MvvmBaseForm + { + #region Constructors + + #region Internal Constructors + + internal LanguageSettingsForm(LanguageSettingsViewModel viewModel) : base(viewModel) + { + InitializeComponent(); + } + + #endregion + + #region Private Constructors + + private LanguageSettingsForm() : this(null!) + { + // this ctor is just for the designer + } + + #endregion + + #endregion + + #region Methods + + #region Static Methods + + private static void OnFormatCultureCommand(ICommandSource source) + { + var culture = (CultureInfo)source.EventArgs.ListItem; + source.EventArgs.Value = $"{culture.EnglishName} ({culture.NativeName})"; + } + + #endregion + + #region Instance Methods + + #region Protected Methods + + protected override void ApplyResources() + { + base.ApplyResources(); + Icon = Properties.Resources.Language; + } + + protected override void ApplyViewModel() + { + InitCommandBindings(); + InitPropertyBindings(); + base.ApplyViewModel(); + } + + #endregion + + #region Private Methods + + private void InitPropertyBindings() + { + // VM.AllowResXResources <-> gbAllowResxResources.Checked + CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.AllowResXResources), gbAllowResxResources, nameof(gbAllowResxResources.Checked)); + + // VM.UseOSLanguage <-> chbUseOSLanguage.Checked + CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.UseOSLanguage), chbUseOSLanguage, nameof(chbUseOSLanguage.Checked)); + + // VM.UseOSLanguage <-> chbUseOSLanguage.Checked + CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.ExistingLanguagesOnly), chbExistingResourcesOnly, nameof(chbExistingResourcesOnly.Checked)); + + // VM.UseOSLanguage -> !cmbLanguages.Enabled + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.UseOSLanguage), nameof(cmbLanguages.Enabled), b => !((bool)b!), cmbLanguages); + + // VM.Languages -> cmbLanguages.DataSource + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Languages), nameof(cmbLanguages.DataSource), cmbLanguages); + + // VM.CurrentLanguage -> cmbLanguages.SelectedItem (cannot use two-way for SelectedItem because there is no SelectedItemChanged event) + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.CurrentLanguage), nameof(cmbLanguages.SelectedItem), cmbLanguages); + + // cmbLanguages.SelectedValue -> VM.CurrentLanguage (cannot use two-way for SelectedValue because ValueMember is not set) + CommandBindings.AddPropertyBinding(cmbLanguages, nameof(cmbLanguages.SelectedValue), nameof(ViewModel.CurrentLanguage), ViewModel); + } + + private void InitCommandBindings() + { + CommandBindings.Add(OnFormatCultureCommand) + .AddSource(cmbLanguages, nameof(cmbLanguages.Format)); + + CommandBindings.Add(ViewModel.SaveConfigCommand) + .AddSource(okCancelApplyButtons.OKButton, nameof(okCancelApplyButtons.OKButton.Click)); + + CommandBindings.Add(ViewModel.ApplyCommand, ViewModel.ApplyCommandState) + .AddSource(okCancelApplyButtons.ApplyButton, nameof(okCancelApplyButtons.ApplyButton.Click)); + + CommandBindings.Add(ViewModel.EditResourcesCommand, ViewModel.EditResourcesCommandState) + .AddSource(btnEditResources, nameof(btnEditResources.Click)); + } + + #endregion + + #endregion + + #endregion + } +} diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs index 4614acf..cf66e6e 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs @@ -27,7 +27,7 @@ private void InitializeComponent() this.tbPath = new System.Windows.Forms.TextBox(); this.lblPath = new System.Windows.Forms.Label(); this.gbVisualStudioVersions = new System.Windows.Forms.GroupBox(); - this.cbInstallations = new System.Windows.Forms.ComboBox(); + this.cmbInstallations = new System.Windows.Forms.ComboBox(); this.gbAvailableVersion = new System.Windows.Forms.GroupBox(); this.lblAvailableVersion = new System.Windows.Forms.Label(); this.gbInstallation.SuspendLayout(); @@ -146,7 +146,7 @@ private void InitializeComponent() // // gbVisualStudioVersions // - this.gbVisualStudioVersions.Controls.Add(this.cbInstallations); + this.gbVisualStudioVersions.Controls.Add(this.cmbInstallations); this.gbVisualStudioVersions.Dock = System.Windows.Forms.DockStyle.Top; this.gbVisualStudioVersions.Location = new System.Drawing.Point(3, 43); this.gbVisualStudioVersions.Name = "gbVisualStudioVersions"; @@ -155,16 +155,16 @@ private void InitializeComponent() this.gbVisualStudioVersions.TabStop = false; this.gbVisualStudioVersions.Text = "gbVisualStudioVersions"; // - // cbInstallations + // cmbInstallations // - this.cbInstallations.Dock = System.Windows.Forms.DockStyle.Top; - this.cbInstallations.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cbInstallations.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.cbInstallations.FormattingEnabled = true; - this.cbInstallations.Location = new System.Drawing.Point(3, 16); - this.cbInstallations.Name = "cbInstallations"; - this.cbInstallations.Size = new System.Drawing.Size(372, 21); - this.cbInstallations.TabIndex = 0; + this.cmbInstallations.Dock = System.Windows.Forms.DockStyle.Top; + this.cmbInstallations.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbInstallations.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.cmbInstallations.FormattingEnabled = true; + this.cmbInstallations.Location = new System.Drawing.Point(3, 16); + this.cmbInstallations.Name = "cmbInstallations"; + this.cmbInstallations.Size = new System.Drawing.Size(372, 21); + this.cmbInstallations.TabIndex = 0; // // gbAvailableVersion // @@ -225,7 +225,7 @@ private void InitializeComponent() private System.Windows.Forms.TextBox tbPath; private System.Windows.Forms.Label lblPath; private GroupBox gbVisualStudioVersions; - private ComboBox cbInstallations; + private ComboBox cmbInstallations; private GroupBox gbAvailableVersion; private Label lblAvailableVersion; } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs index 73fed3f..4753bad 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs @@ -35,8 +35,8 @@ internal ManageInstallationsForm(ManageInstallationsViewModel viewModel) : base(viewModel) { InitializeComponent(); - cbInstallations.ValueMember = nameof(KeyValuePair.Key); - cbInstallations.DisplayMember = nameof(KeyValuePair.Value); + cmbInstallations.ValueMember = nameof(KeyValuePair.Key); + cmbInstallations.DisplayMember = nameof(KeyValuePair.Value); } #endregion @@ -89,10 +89,10 @@ private void InitViewModelDependencies() private void InitPropertyBindings() { // will not change so not as an actual binding - cbInstallations.DataSource = ViewModel.Installations; + cmbInstallations.DataSource = ViewModel.Installations; - // VM.SelectedInstallation <-> cbInstallations.SelectedValue - CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.SelectedInstallation), cbInstallations, nameof(cbInstallations.SelectedValue)); + // VM.SelectedInstallation <-> cmbInstallations.SelectedValue + CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.SelectedInstallation), cmbInstallations, nameof(cmbInstallations.SelectedValue)); // VM.CurrentPath <-> tbPath.Text CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.CurrentPath), tbPath, nameof(tbPath.Text)); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index 2b8f1a6..9d71052 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -106,9 +106,15 @@ protected override void OnLoad(EventArgs e) ApplyViewModel(); } - protected virtual void ApplyResources() => this.ApplyStringResources(toolTip); + protected virtual void ApplyResources() => ApplyStringResources(); - protected virtual void ApplyViewModel() => VM.ViewLoaded(); + protected virtual void ApplyStringResources() => this.ApplyStringResources(toolTip); + + protected virtual void ApplyViewModel() + { + InitCommandBindings(); + VM.ViewLoaded(); + } protected override void OnFormClosing(FormClosingEventArgs e) { @@ -132,6 +138,12 @@ protected override void Dispose(bool disposing) #region Private Methods + private void InitCommandBindings() + { + CommandBindings.Add(ApplyStringResources) + .AddSource(typeof(LanguageSettings), nameof(LanguageSettings.DisplayLanguageChanged)); + } + private void ShowChildView(IViewModel vm) => ViewFactory.ShowDialog(vm, Handle); private void InvokeIfRequired(Action action) diff --git a/KGySoft.Drawing.ImagingTools/View/Images.cs b/KGySoft.Drawing.ImagingTools/View/Images.cs index a6c30d2..450c74c 100644 --- a/KGySoft.Drawing.ImagingTools/View/Images.cs +++ b/KGySoft.Drawing.ImagingTools/View/Images.cs @@ -34,6 +34,7 @@ internal static class Images private static Bitmap? check; private static Bitmap? crop; private static Bitmap? highlightVisibleClip; + private static Bitmap? language; private static Bitmap? magnifier; private static Bitmap? magnifierPlus; private static Bitmap? magnifierMinus; @@ -64,6 +65,7 @@ internal static class Images internal static Bitmap Check => check ??= GetResource(nameof(Check)); internal static Bitmap Crop => crop ??= GetResource(nameof(Crop)); internal static Bitmap HighlightVisibleClip => highlightVisibleClip ??= GetResource(nameof(HighlightVisibleClip)); + internal static Bitmap Language => language ??= GetResource(nameof(Language)); internal static Bitmap Magnifier => magnifier ??= GetResource(nameof(Magnifier)); internal static Bitmap MagnifierPlus => magnifierPlus ??= GetResource(nameof(MagnifierPlus)); internal static Bitmap MagnifierMinus => magnifierMinus ??= GetResource(nameof(MagnifierMinus)); diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelApplyButtons.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelApplyButtons.cs new file mode 100644 index 0000000..97eaf6c --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelApplyButtons.cs @@ -0,0 +1,58 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: OkCancelApplyButtons.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2020 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System.Windows.Forms; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View.UserControls +{ + internal class OkCancelApplyButtons : OkCancelButtons + { + #region Properties + + internal Button ApplyButton { get; } + + #endregion + + #region Constructors + + public OkCancelApplyButtons() + { + pnlButtons.SuspendLayout(); + pnlButtons.ColumnCount = 3; + foreach (ColumnStyle columnStyle in pnlButtons.ColumnStyles) + columnStyle.Width = 33.3f; + pnlButtons.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 33.3f)); + ApplyButton = new Button + { + Anchor = AnchorStyles.None, + FlatStyle = FlatStyle.System, + Name = "btnApply", + TabIndex = 2, + Text = @"btnApply", + UseVisualStyleBackColor = true + + }; + pnlButtons.Controls.Add(ApplyButton, 2, 0); + pnlButtons.ResumeLayout(false); + } + + #endregion + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs index 1c89482..84138f1 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs @@ -73,9 +73,8 @@ private void InitializeComponent() } #endregion - - private System.Windows.Forms.TableLayoutPanel pnlButtons; private System.Windows.Forms.Button btnOK; private System.Windows.Forms.Button btnCancel; + protected System.Windows.Forms.TableLayoutPanel pnlButtons; } } diff --git a/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs b/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs index 6a9bef9..dbe6635 100644 --- a/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs +++ b/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs @@ -54,35 +54,23 @@ public static IView CreateView(IViewModel viewModel) if (viewModel == null) throw new ArgumentNullException(nameof(viewModel), PublicResources.ArgumentNull); - switch (viewModel) + return viewModel switch { - case DefaultViewModel defaultViewModel: - return new AppMainForm(defaultViewModel); - case GraphicsVisualizerViewModel graphicsVisualizerViewModel: - return new GraphicsVisualizerForm(graphicsVisualizerViewModel); - case ImageVisualizerViewModel imageVisualizerViewModel: // also for BitmapData - return new ImageVisualizerForm(imageVisualizerViewModel); - case PaletteVisualizerViewModel paletteVisualizerViewModel: - return new PaletteVisualizerForm(paletteVisualizerViewModel); - case ColorVisualizerViewModel colorVisualizerViewModel: - return new ColorVisualizerForm(colorVisualizerViewModel); - case ManageInstallationsViewModel manageInstallationsViewModel: - return new ManageInstallationsForm(manageInstallationsViewModel); - case ResizeBitmapViewModel resizeBitmapViewModel: - return new ResizeBitmapForm(resizeBitmapViewModel); - case ColorSpaceViewModel colorSpaceViewModel: - return new ColorSpaceForm(colorSpaceViewModel); - case CountColorsViewModel countColorsViewModel: - return new CountColorsForm(countColorsViewModel); - case AdjustBrightnessViewModel adjustBrightnessViewModel: - return new AdjustBrightnessForm(adjustBrightnessViewModel); - case AdjustContrastViewModel adjustContrastViewModel: - return new AdjustContrastForm(adjustContrastViewModel); - case AdjustGammaViewModel adjustGammaViewModel: - return new AdjustGammaForm(adjustGammaViewModel); - default: - throw new InvalidOperationException(Res.InternalError($"Unexpected viewModel type: {viewModel.GetType()}")); - } + DefaultViewModel defaultViewModel => new AppMainForm(defaultViewModel), + GraphicsVisualizerViewModel graphicsVisualizerViewModel => new GraphicsVisualizerForm(graphicsVisualizerViewModel), + ImageVisualizerViewModel imageVisualizerViewModel => new ImageVisualizerForm(imageVisualizerViewModel), // also for BitmapData + PaletteVisualizerViewModel paletteVisualizerViewModel => new PaletteVisualizerForm(paletteVisualizerViewModel), + ColorVisualizerViewModel colorVisualizerViewModel => new ColorVisualizerForm(colorVisualizerViewModel), + ManageInstallationsViewModel manageInstallationsViewModel => new ManageInstallationsForm(manageInstallationsViewModel), + ResizeBitmapViewModel resizeBitmapViewModel => new ResizeBitmapForm(resizeBitmapViewModel), + ColorSpaceViewModel colorSpaceViewModel => new ColorSpaceForm(colorSpaceViewModel), + CountColorsViewModel countColorsViewModel => new CountColorsForm(countColorsViewModel), + AdjustBrightnessViewModel adjustBrightnessViewModel => new AdjustBrightnessForm(adjustBrightnessViewModel), + AdjustContrastViewModel adjustContrastViewModel => new AdjustContrastForm(adjustContrastViewModel), + AdjustGammaViewModel adjustGammaViewModel => new AdjustGammaForm(adjustGammaViewModel), + LanguageSettingsViewModel languageSettingsViewModel => new LanguageSettingsForm(languageSettingsViewModel), + _ => throw new InvalidOperationException(Res.InternalError($"Unexpected viewModel type: {viewModel.GetType()}")) + }; } /// diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs index 6e32f37..0b791bb 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs @@ -141,6 +141,7 @@ internal ImageInfo ImageInfo internal ICommand NextImageCommand => Get(() => new SimpleCommand(OnNextImageCommand)); internal ICommand ShowPaletteCommand => Get(() => new SimpleCommand(OnShowPaletteCommand)); internal ICommand ManageInstallationsCommand => Get(() => new SimpleCommand(OnManageInstallationsCommand)); + internal ICommand SetLanguageCommand => Get(() => new SimpleCommand(OnSetLanguageCommand)); internal ICommand RotateLeftCommand => Get(() => new SimpleCommand(OnRotateLeftCommand)); internal ICommand RotateRightCommand => Get(() => new SimpleCommand(OnRotateRightCommand)); internal ICommand ResizeBitmapCommand => Get(() => new SimpleCommand(OnResizeBitmapCommand)); @@ -1171,6 +1172,12 @@ private void OnCountColorsCommand() private void OnAdjustContrastCommand() => EditBitmap(ViewModelFactory.CreateAdjustContrast); private void OnAdjustGammaCommand() => EditBitmap(ViewModelFactory.CreateAdjustGamma); + private void OnSetLanguageCommand() + { + using IViewModel viewModel = ViewModelFactory.CreateLanguageSettings(); + ShowChildViewCallback?.Invoke(viewModel); + } + #endregion #endregion diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs new file mode 100644 index 0000000..e0eb0f0 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -0,0 +1,195 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: LanguageSettingsViewModel.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; + +using KGySoft.ComponentModel; +using KGySoft.Drawing.ImagingTools.Properties; +using KGySoft.Resources; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.ViewModel +{ + internal class LanguageSettingsViewModel : ViewModelBase + { + #region Fields + + private readonly bool initializing; + + private CultureInfo activeLanguage; + private CultureInfo[]? neutralLanguages; + + #endregion + + #region Properties + + #region Internal Properties + + internal bool AllowResXResources { get => Get(); set => Set(value); } + internal bool UseOSLanguage { get => Get(); set => Set(value); } + internal bool ExistingLanguagesOnly { get => Get(); set => Set(value); } + internal CultureInfo CurrentLanguage { get => Get(); set => Set(value); } + internal IList Languages { get => Get>(); set => Set(value); } + + internal ICommand ApplyCommand => Get(() => new SimpleCommand(OnApplyCommand)); + internal ICommand SaveConfigCommand => Get(() => new SimpleCommand(OnSaveConfigCommand)); + internal ICommand EditResourcesCommand => Get(() => new SimpleCommand(OnEditResourcesCommand)); + + internal ICommandState ApplyCommandState => Get(() => new CommandState()); + internal ICommandState EditResourcesCommandState => Get(() => new CommandState()); + + #endregion + + #region Private Properties + + private CultureInfo[] NeutralLanguages => neutralLanguages ??= CultureInfo.GetCultures(CultureTypes.NeutralCultures); + + #endregion + + #endregion + + #region Constructors + + internal LanguageSettingsViewModel() + { + initializing = true; + CurrentLanguage = activeLanguage = LanguageSettings.DisplayLanguage; + AllowResXResources = Settings.Default.AllowResXResources; + UseOSLanguage = Settings.Default.UseOSLanguage; + ExistingLanguagesOnly = true; // could be the default value but this way we spare one reset when initializing binding + initializing = false; + ResetLanguages(); + } + + #endregion + + #region Methods + + #region Protected Methods + + protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) + { + base.OnPropertyChanged(e); + switch (e.PropertyName) + { + case nameof(AllowResXResources): + case nameof(UseOSLanguage): + case nameof(ExistingLanguagesOnly): + if (initializing) + return; + ResetLanguages(); + break; + + case nameof(Languages): + if (e.OldValue is SortableBindingList sbl) + sbl.Dispose(); + break; + + case nameof(CurrentLanguage): + ApplyCommandState.Enabled = !Equals(e.NewValue, activeLanguage); + EditResourcesCommandState.Enabled = !Equals(e.NewValue, Res.DefaultLanguage); + break; + } + } + + protected override void Dispose(bool disposing) + { + if (IsDisposed) + return; + + if (disposing) + (Languages as SortableBindingList)?.Dispose(); + + base.Dispose(disposing); + } + + #endregion + + #region Private Methods + + private void ResetLanguages() + { + if (!AllowResXResources) + { + Languages = new[] { Res.DefaultLanguage }; + CurrentLanguage = Res.DefaultLanguage; + return; + } + + if (UseOSLanguage) + { + Languages = new[] { Res.OSLanguage }; + CurrentLanguage = Res.OSLanguage; + return; + } + + var result = new SortableBindingList(ExistingLanguagesOnly ? Res.GetAvailableLanguages() : NeutralLanguages); + result.ApplySort(nameof(CultureInfo.EnglishName), ListSortDirection.Ascending); + CultureInfo lastSelectedLanguage = CurrentLanguage; + Languages = result; + CurrentLanguage = result.Contains(lastSelectedLanguage) ? lastSelectedLanguage : Res.DefaultLanguage; + } + + #endregion + + #region Command Handlers + + private void OnApplyCommand() + { + LanguageSettings.DynamicResourceManagersSource = AllowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; + LanguageSettings.DisplayLanguage = CurrentLanguage; + ApplyCommandState.Enabled = false; + } + + private void OnSaveConfigCommand() + { + if (!IsModified) + return; + + if (ApplyCommandState.Enabled) + OnApplyCommand(); + + // saving the configuration + Settings.Default.AllowResXResources = AllowResXResources; + Settings.Default.UseOSLanguage = UseOSLanguage; + Settings.Default.DisplayLanguage = activeLanguage = CurrentLanguage; + try + { + Settings.Default.Save(); + } + catch (Exception e) when (!e.IsCritical()) + { + ShowError(Res.ErrorMessageFailedToSaveSettings(e.Message)); + } + } + + private void OnEditResourcesCommand() + { + //using IViewModel viewModel = ViewModelFactory.CreateEditResources(CurrentLanguage); + //ShowChildViewCallback?.Invoke(viewModel); + } + + #endregion + + #endregion + } +} diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs index eea552a..4e0773b 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs @@ -17,8 +17,9 @@ #region Usings using System.Drawing; -using System.Drawing.Imaging; +using System.Drawing.Imaging; +using System.Globalization; using KGySoft.Drawing.ImagingTools.Model; #endregion @@ -31,6 +32,12 @@ namespace KGySoft.Drawing.ImagingTools.ViewModel /// public static class ViewModelFactory { + #region Constructors + + static ViewModelFactory() => Res.EnsureInitialized(); + + #endregion + #region Methods /// @@ -163,7 +170,7 @@ public static IViewModel FromBitmapData(BitmapData? bitmapData) /// The graphics. /// An instance that represents a view model for a . public static IViewModel FromGraphics(Graphics? graphics) - => new GraphicsVisualizerViewModel { GraphicsInfo = graphics == null ? null : new GraphicsInfo(graphics)}; + => new GraphicsVisualizerViewModel { GraphicsInfo = graphics == null ? null : new GraphicsInfo(graphics) }; /// /// Creates a view model for a from arbitrary debug information. @@ -214,6 +221,18 @@ public static IViewModel FromGraphics(Graphics? graphics) /// An instance that represents a view model for adjusting the gamma of a . public static IViewModel CreateAdjustGamma(Bitmap bitmap) => new AdjustGammaViewModel(bitmap); + /// + /// Creates a view model for managing language settings. + /// + /// An instance that represents a view model for managing language settings. + public static IViewModel CreateLanguageSettings() => new LanguageSettingsViewModel(); + + ///// + ///// Creates a view model for managing language settings. + ///// + ///// An instance that represents a view model for managing language settings. + //public static IViewModel CreateEditResources(CultureInfo culture) => new EditResourcesViewModel(culture); + #endregion } } \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index 7c74070..ffecc9f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -20,6 +20,8 @@ - Fixing possible errors when closing forms while an async operation is still in progress. * API changes: * Members are annotated for using C# 8.0 nullable references + + ViewModelFactory class: + + New CreateLanguageSettings method - KGySoft.Drawing.DebuggerVisualizers.dll ========================================= From be940312e82f3096e90635993418857d1e7b49eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 20 May 2021 19:38:12 +0200 Subject: [PATCH 034/211] Adjusting name prefixes --- .../View/DebuggerTestForm.Designer.cs | 22 +++++++++---------- .../View/DebuggerTestForm.cs | 12 +++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.Designer.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.Designer.cs index 2087f51..33f4853 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.Designer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.Designer.cs @@ -19,7 +19,7 @@ private void InitializeComponent() { this.btnViewDirect = new System.Windows.Forms.Button(); this.btnViewByDebugger = new System.Windows.Forms.Button(); - this.tbFile = new System.Windows.Forms.TextBox(); + this.txtFile = new System.Windows.Forms.TextBox(); this.rbFromFile = new System.Windows.Forms.RadioButton(); this.rbManagedIcon = new System.Windows.Forms.RadioButton(); this.rbHIcon = new System.Windows.Forms.RadioButton(); @@ -67,15 +67,15 @@ private void InitializeComponent() this.btnViewByDebugger.Text = "View by Debugger"; this.btnViewByDebugger.UseVisualStyleBackColor = true; // - // tbFile + // txtFile // - this.tbFile.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend; - this.tbFile.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.FileSystem; - this.tbFile.Dock = System.Windows.Forms.DockStyle.Top; - this.tbFile.Location = new System.Drawing.Point(3, 16); - this.tbFile.Name = "tbFile"; - this.tbFile.Size = new System.Drawing.Size(190, 20); - this.tbFile.TabIndex = 0; + this.txtFile.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend; + this.txtFile.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.FileSystem; + this.txtFile.Dock = System.Windows.Forms.DockStyle.Top; + this.txtFile.Location = new System.Drawing.Point(3, 16); + this.txtFile.Name = "txtFile"; + this.txtFile.Size = new System.Drawing.Size(190, 20); + this.txtFile.TabIndex = 0; // // rbFromFile // @@ -194,7 +194,7 @@ private void InitializeComponent() this.gbFile.Controls.Add(this.rbAsMetafile); this.gbFile.Controls.Add(this.rbAsBitmap); this.gbFile.Controls.Add(this.rbAsImage); - this.gbFile.Controls.Add(this.tbFile); + this.gbFile.Controls.Add(this.txtFile); this.gbFile.Dock = System.Windows.Forms.DockStyle.Top; this.gbFile.Enabled = false; this.gbFile.Location = new System.Drawing.Point(0, 219); @@ -367,7 +367,7 @@ private void InitializeComponent() private RadioButton rbHIcon; private RadioButton rbManagedIcon; private RadioButton rbFromFile; - private TextBox tbFile; + private TextBox txtFile; private Button btnViewByDebugger; private Button btnViewDirect; private PictureBox pictureBox; diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.cs index 84b69a8..7718017 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.cs @@ -60,7 +60,7 @@ public DebuggerTestForm() commandBindings.AddPropertyBinding(rbColor, nameof(RadioButton.Checked), nameof(viewModel.SingleColor), viewModel); commandBindings.AddPropertyBinding(rbFromFile, nameof(RadioButton.Checked), nameof(viewModel.ImageFromFile), viewModel); - commandBindings.AddPropertyBinding(tbFile, nameof(tbFile.Text), nameof(viewModel.FileName), viewModel); + commandBindings.AddPropertyBinding(txtFile, nameof(txtFile.Text), nameof(viewModel.FileName), viewModel); commandBindings.AddPropertyBinding(rbAsImage, nameof(RadioButton.Checked), nameof(viewModel.FileAsImage), viewModel); commandBindings.AddPropertyBinding(rbAsBitmap, nameof(RadioButton.Checked), nameof(viewModel.FileAsBitmap), viewModel); commandBindings.AddPropertyBinding(rbAsMetafile, nameof(RadioButton.Checked), nameof(viewModel.FileAsMetafile), viewModel); @@ -76,8 +76,8 @@ public DebuggerTestForm() commandBindings.AddPropertyBinding(viewModel, nameof(viewModel.PreviewImage), nameof(pictureBox.Image), pictureBox); commandBindings.Add(OnSelectFileCommand) - .AddSource(tbFile, nameof(tbFile.Click)) - .AddSource(tbFile, nameof(tbFile.DoubleClick)); + .AddSource(txtFile, nameof(txtFile.Click)) + .AddSource(txtFile, nameof(txtFile.DoubleClick)); commandBindings.Add(viewModel.DirectViewCommand).AddSource(btnViewDirect, nameof(btnViewDirect.Click)); commandBindings.Add(viewModel.DebugCommand).AddSource(btnViewByDebugger, nameof(btnViewByDebugger.Click)); @@ -126,12 +126,12 @@ protected override void Dispose(bool disposing) private void OnSelectFileCommand(ICommandSource source) { // simple click opens the file dialog only if text was empty - if (tbFile.Text.Length != 0 && source.TriggeringEvent == nameof(tbFile.Click)) + if (txtFile.Text.Length != 0 && source.TriggeringEvent == nameof(txtFile.Click)) return; - using (var ofd = new OpenFileDialog { FileName = tbFile.Text }) + using (var ofd = new OpenFileDialog { FileName = txtFile.Text }) { if (ofd.ShowDialog() == DialogResult.OK) - tbFile.Text = ofd.FileName; + txtFile.Text = ofd.FileName; } } From 0bcfc13c238c5df70df5007cfc4242034a1e87b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 20 May 2021 19:40:06 +0200 Subject: [PATCH 035/211] Editing resources --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 20 +- .../Model/ResourceEntry.cs | 46 +++ ...magingTools.Model.ResourceEntry.datasource | 10 + KGySoft.Drawing.ImagingTools/Res.cs | 57 +--- .../View/Forms/EditResourcesForm.Designer.cs | 270 ++++++++++++++++++ .../View/Forms/EditResourcesForm.cs | 109 +++++++ .../View/ViewFactory.cs | 1 + .../ViewModel/EditResourcesViewModel.cs | 243 ++++++++++++++++ .../ViewModel/LanguageSettingsViewModel.cs | 14 +- .../ViewModel/ViewModelFactory.cs | 10 +- .../_Classes/ResHelper.cs | 134 +++++++++ changelog.txt | 1 + 12 files changed, 863 insertions(+), 52 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs create mode 100644 KGySoft.Drawing.ImagingTools/Properties/DataSources/KGySoft.Drawing.ImagingTools.Model.ResourceEntry.datasource create mode 100644 KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs create mode 100644 KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs create mode 100644 KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs create mode 100644 KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 4c824b5..061ef6a 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -159,6 +159,9 @@ Color: {0} + + Edit Resources - {0} + files @@ -381,6 +384,12 @@ Either select at least '{1}' or reduce the number of colors to {2}. Failed to save settings: {0} + + Failed to regenerate resource file {0}: {1} + + + Failed to save resource file {0}: {1} + Could not create directory {0}: {1} @@ -419,6 +428,11 @@ so partial transparent pixels will be blended with back color. The extension of the provided filename '{0}' does not match to the selected format ({1}). Are you sure you want to save the file with the provided extension? + + + Failed to read resource file {0}: {1} + +Do you want to try to regenerate it? The current file will be deleted. Are you sure you want to remove this installation? @@ -663,6 +677,9 @@ Dithering may help to preserve more details. &Cancel + + &Apply + &Close @@ -766,7 +783,8 @@ Dithering may help to preserve more details. Show Languages Only With Existing Resources - When checked, enlists languages only with existing resource files. Uncheck to allow generating resources for any language. + When checked, enlists languages only with existing resource files. Uncheck to allow generating resources for any language. +Newly generated resources will contain English texts prefixed with "[T]". You can then edit the resources to translate these texts. Display Language diff --git a/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs b/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs new file mode 100644 index 0000000..b8847b6 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs @@ -0,0 +1,46 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: ResourceEntry.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using KGySoft.ComponentModel; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.Model +{ + internal class ResourceEntry : ObservableObjectBase + { + #region Properties + + public string Key { get; } + public string OriginalText { get; } + public string TranslatedText { get => Get(); set => Set(value); } + + #endregion + + #region Constructors + + internal ResourceEntry(string key, string originalText, string translatedText) + { + Key = key; + OriginalText = originalText; + TranslatedText = translatedText; + } + + #endregion + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/Properties/DataSources/KGySoft.Drawing.ImagingTools.Model.ResourceEntry.datasource b/KGySoft.Drawing.ImagingTools/Properties/DataSources/KGySoft.Drawing.ImagingTools.Model.ResourceEntry.datasource new file mode 100644 index 0000000..b815936 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/Properties/DataSources/KGySoft.Drawing.ImagingTools.Model.ResourceEntry.datasource @@ -0,0 +1,10 @@ + + + + KGySoft.Drawing.ImagingTools.Model.ResourceEntry, KGySoft.Drawing.ImagingTools, Version=2.3.0.0, Culture=neutral, PublicKeyToken=b45eba277439ddfe + \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 4d04537..ce4e2b1 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -17,12 +17,10 @@ #region Usings using System; -using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; using System.Globalization; -using System.IO; using System.Linq; using System.Reflection; using System.Resources; @@ -58,18 +56,15 @@ internal static class Res // ReSharper disable once CollectionNeverUpdated.Local private static readonly Cache localizableStringPropertiesCache = new Cache(GetLocalizableStringProperties); - private static StringKeyedDictionary? culturesCache; - #endregion #region Properties - #region Internal Properties - #region General internal static CultureInfo OSLanguage { get; } internal static CultureInfo DefaultLanguage { get; } + internal static string ResourcesDir => resourceManager.ResXResourcesDir; #endregion @@ -240,15 +235,6 @@ internal static class Res #endregion - #region Private Properties - - private static StringKeyedDictionary CulturesCache - => culturesCache ??= CultureInfo.GetCultures(CultureTypes.AllCultures).ToStringKeyedDictionary(ci => ci.Name); - - #endregion - - #endregion - #region Constructors static Res() @@ -281,33 +267,6 @@ internal static void EnsureInitialized() { } - internal static IList GetAvailableLanguages() - { - string dir = resourceManager.ResXResourcesDir; - try - { - var result = new List { DefaultLanguage }; - if (!Directory.Exists(dir)) - return result; - - string baseName = resourceManager.BaseName; - int startIndex = dir.Length + baseName.Length + 2; - string[] files = Directory.GetFiles(dir, $"{baseName}.*.resx", SearchOption.TopDirectoryOnly); - foreach (string file in files) - { - StringSegment resName = file.AsSegment(startIndex, file.Length - startIndex - 5); - if (CulturesCache.TryGetValue(resName, out CultureInfo? ci) && !ci.In(CultureInfo.InvariantCulture, DefaultLanguage)) - result.Add(ci); - } - - return result; - } - catch (Exception e) when (!e.IsCritical()) - { - return new[] { DefaultLanguage }; - } - } - internal static string? GetStringOrNull(string id) => resourceManager.GetString(id, LanguageSettings.DisplayLanguage); internal static string Get(string id) => GetStringOrNull(id) ?? String.Format(CultureInfo.InvariantCulture, unavailableResource, id); @@ -359,6 +318,9 @@ internal static void ApplyStringResources(object target, string name) /// Color: {0} internal static string TitleColor(Color color) => Get("Title_ColorFormat", color.Name); + /// Edit Resources - {0} + internal static string TitleEditResources(string langName) => Get("Title_EditResourcesFormat", langName); + #endregion #region Texts @@ -515,6 +477,12 @@ internal static string InfoColor(int argb, string knownColors, string systemColo /// Failed to save settings: {0} internal static string ErrorMessageFailedToSaveSettings(string message) => Get("ErrorMessage_FailedToSaveSettingsFormat", message); + /// Failed to regenerate resource file {0}: {1} + internal static string ErrorMessageFailedToRegenerateResource(string fileName, string message) => Get("ErrorMessage_FailedToRegenerateResourceFormat", fileName, message); + + /// Failed to save resource file {0}: {1} + internal static string ErrorMessageFailedToSaveResource(string fileName, string message) => Get("ErrorMessage_FailedToSaveResourceFormat", fileName, message); + /// Could not create directory {0}: {1} /// /// The debugger visualizer may will not work for .NET Core projects. @@ -547,6 +515,11 @@ internal static string InfoColor(int argb, string knownColors, string systemColo /// Are you sure you want to save the file with the provided extension? internal static string ConfirmMessageSaveFileExtension(string fileName, string format) => Get("ConfirmMessage_SaveFileExtensionFormat", fileName, format); + /// Failed to read resource file {0}: {1} + /// + /// Do you want to try to regenerate it? The current file will be deleted. + internal static string ConfirmMessageTryRegenerateResource(string fileName, string message) => Get("ConfirmMessage_TryRegenerateResourceFormat", fileName, message); + /// {0} is the lowest compatible pixel format, which still supports the selected quantizer. internal static string InfoMessagePixelFormatUnnecessarilyWide(PixelFormat pixelFormat) => Get("InfoMessage_PixelFormatUnnecessarilyWideFormat", pixelFormat); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs new file mode 100644 index 0000000..0f64cd9 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -0,0 +1,270 @@ + +namespace KGySoft.Drawing.ImagingTools.View.Forms +{ + partial class EditResourcesForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); + this.gbResourceEntries = new System.Windows.Forms.GroupBox(); + this.gridResources = new System.Windows.Forms.DataGridView(); + this.resourceEntryBindingSource = new System.Windows.Forms.BindingSource(this.components); + this.gbResourceFile = new System.Windows.Forms.GroupBox(); + this.cmbResourceFiles = new System.Windows.Forms.ComboBox(); + this.splitterEditResources = new System.Windows.Forms.Splitter(); + this.pnlEditResourceEntry = new System.Windows.Forms.TableLayoutPanel(); + this.gbOriginalText = new System.Windows.Forms.GroupBox(); + this.txtOriginalText = new System.Windows.Forms.TextBox(); + this.gbTranslatedText = new System.Windows.Forms.GroupBox(); + this.txtTranslatedText = new System.Windows.Forms.TextBox(); + this.okCancelButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); + this.colResourceKey = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colOriginalText = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colTranslatedText = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.gbResourceEntries.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.gridResources)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.resourceEntryBindingSource)).BeginInit(); + this.gbResourceFile.SuspendLayout(); + this.pnlEditResourceEntry.SuspendLayout(); + this.gbOriginalText.SuspendLayout(); + this.gbTranslatedText.SuspendLayout(); + this.SuspendLayout(); + // + // gbResourceEntries + // + this.gbResourceEntries.Controls.Add(this.gridResources); + this.gbResourceEntries.Dock = System.Windows.Forms.DockStyle.Fill; + this.gbResourceEntries.Location = new System.Drawing.Point(3, 49); + this.gbResourceEntries.Name = "gbResourceEntries"; + this.gbResourceEntries.Size = new System.Drawing.Size(578, 112); + this.gbResourceEntries.TabIndex = 2; + this.gbResourceEntries.TabStop = false; + this.gbResourceEntries.Text = "gbResourceEntries"; + // + // gridResources + // + this.gridResources.AllowUserToAddRows = false; + this.gridResources.AllowUserToDeleteRows = false; + dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.ControlLight; + this.gridResources.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1; + this.gridResources.AutoGenerateColumns = false; + this.gridResources.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.gridResources.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { + this.colResourceKey, + this.colOriginalText, + this.colTranslatedText}); + this.gridResources.DataSource = this.resourceEntryBindingSource; + dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; + dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window; + dataGridViewCellStyle2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.ControlText; + dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight; + dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText; + dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.gridResources.DefaultCellStyle = dataGridViewCellStyle2; + this.gridResources.Dock = System.Windows.Forms.DockStyle.Fill; + this.gridResources.Location = new System.Drawing.Point(3, 16); + this.gridResources.Name = "gridResources"; + this.gridResources.Size = new System.Drawing.Size(572, 93); + this.gridResources.TabIndex = 3; + // + // resourceEntryBindingSource + // + this.resourceEntryBindingSource.DataSource = typeof(KGySoft.Drawing.ImagingTools.Model.ResourceEntry); + // + // gbResourceFile + // + this.gbResourceFile.Controls.Add(this.cmbResourceFiles); + this.gbResourceFile.Dock = System.Windows.Forms.DockStyle.Top; + this.gbResourceFile.Location = new System.Drawing.Point(3, 3); + this.gbResourceFile.Name = "gbResourceFile"; + this.gbResourceFile.Size = new System.Drawing.Size(578, 46); + this.gbResourceFile.TabIndex = 5; + this.gbResourceFile.TabStop = false; + this.gbResourceFile.Text = "gbResourceFile"; + // + // cmbResourceFiles + // + this.cmbResourceFiles.Dock = System.Windows.Forms.DockStyle.Fill; + this.cmbResourceFiles.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbResourceFiles.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.cmbResourceFiles.FormattingEnabled = true; + this.cmbResourceFiles.Location = new System.Drawing.Point(3, 16); + this.cmbResourceFiles.Name = "cmbResourceFiles"; + this.cmbResourceFiles.Size = new System.Drawing.Size(572, 21); + this.cmbResourceFiles.TabIndex = 2; + // + // splitterEditResources + // + this.splitterEditResources.Dock = System.Windows.Forms.DockStyle.Bottom; + this.splitterEditResources.Location = new System.Drawing.Point(3, 161); + this.splitterEditResources.MinExtra = 50; + this.splitterEditResources.MinSize = 50; + this.splitterEditResources.Name = "splitterEditResources"; + this.splitterEditResources.Size = new System.Drawing.Size(578, 3); + this.splitterEditResources.TabIndex = 4; + this.splitterEditResources.TabStop = false; + // + // pnlEditResourceEntry + // + this.pnlEditResourceEntry.ColumnCount = 2; + this.pnlEditResourceEntry.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.pnlEditResourceEntry.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.pnlEditResourceEntry.Controls.Add(this.gbOriginalText, 0, 0); + this.pnlEditResourceEntry.Controls.Add(this.gbTranslatedText, 1, 0); + this.pnlEditResourceEntry.Dock = System.Windows.Forms.DockStyle.Bottom; + this.pnlEditResourceEntry.Location = new System.Drawing.Point(3, 164); + this.pnlEditResourceEntry.Name = "pnlEditResourceEntry"; + this.pnlEditResourceEntry.RowCount = 1; + this.pnlEditResourceEntry.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.pnlEditResourceEntry.Size = new System.Drawing.Size(578, 104); + this.pnlEditResourceEntry.TabIndex = 3; + // + // gbOriginalText + // + this.gbOriginalText.Controls.Add(this.txtOriginalText); + this.gbOriginalText.Dock = System.Windows.Forms.DockStyle.Fill; + this.gbOriginalText.Location = new System.Drawing.Point(3, 3); + this.gbOriginalText.Name = "gbOriginalText"; + this.gbOriginalText.Size = new System.Drawing.Size(283, 98); + this.gbOriginalText.TabIndex = 0; + this.gbOriginalText.TabStop = false; + this.gbOriginalText.Text = "gbOriginalText"; + // + // txtOriginalText + // + this.txtOriginalText.Dock = System.Windows.Forms.DockStyle.Fill; + this.txtOriginalText.Location = new System.Drawing.Point(3, 16); + this.txtOriginalText.Multiline = true; + this.txtOriginalText.Name = "txtOriginalText"; + this.txtOriginalText.ReadOnly = true; + this.txtOriginalText.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.txtOriginalText.Size = new System.Drawing.Size(277, 79); + this.txtOriginalText.TabIndex = 0; + this.txtOriginalText.WordWrap = false; + // + // gbTranslatedText + // + this.gbTranslatedText.Controls.Add(this.txtTranslatedText); + this.gbTranslatedText.Dock = System.Windows.Forms.DockStyle.Fill; + this.gbTranslatedText.Location = new System.Drawing.Point(292, 3); + this.gbTranslatedText.Name = "gbTranslatedText"; + this.gbTranslatedText.Size = new System.Drawing.Size(283, 98); + this.gbTranslatedText.TabIndex = 1; + this.gbTranslatedText.TabStop = false; + this.gbTranslatedText.Text = "gbTranslatedText"; + // + // txtTranslatedText + // + this.txtTranslatedText.Dock = System.Windows.Forms.DockStyle.Fill; + this.txtTranslatedText.Location = new System.Drawing.Point(3, 16); + this.txtTranslatedText.Multiline = true; + this.txtTranslatedText.Name = "txtTranslatedText"; + this.txtTranslatedText.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.txtTranslatedText.Size = new System.Drawing.Size(277, 79); + this.txtTranslatedText.TabIndex = 1; + this.txtTranslatedText.WordWrap = false; + // + // okCancelButtons + // + this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; + this.okCancelButtons.Location = new System.Drawing.Point(3, 268); + this.okCancelButtons.Name = "okCancelButtons"; + this.okCancelButtons.Size = new System.Drawing.Size(578, 40); + this.okCancelButtons.TabIndex = 1; + // + // colResourceKey + // + this.colResourceKey.DataPropertyName = "Key"; + this.colResourceKey.HeaderText = "colResourceKey"; + this.colResourceKey.Name = "colResourceKey"; + this.colResourceKey.ReadOnly = true; + // + // colOriginalText + // + this.colOriginalText.DataPropertyName = "OriginalText"; + this.colOriginalText.HeaderText = "colOriginalText"; + this.colOriginalText.Name = "colOriginalText"; + this.colOriginalText.ReadOnly = true; + this.colOriginalText.Width = 200; + // + // colTranslatedText + // + this.colTranslatedText.DataPropertyName = "TranslatedText"; + this.colTranslatedText.HeaderText = "colTranslatedText"; + this.colTranslatedText.Name = "colTranslatedText"; + this.colTranslatedText.Width = 200; + // + // EditResourcesForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(584, 311); + this.Controls.Add(this.gbResourceEntries); + this.Controls.Add(this.gbResourceFile); + this.Controls.Add(this.splitterEditResources); + this.Controls.Add(this.pnlEditResourceEntry); + this.Controls.Add(this.okCancelButtons); + this.MinimizeBox = false; + this.MinimumSize = new System.Drawing.Size(300, 300); + this.Name = "EditResourcesForm"; + this.Padding = new System.Windows.Forms.Padding(3); + this.Text = "EditResourcesForm"; + this.gbResourceEntries.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.gridResources)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.resourceEntryBindingSource)).EndInit(); + this.gbResourceFile.ResumeLayout(false); + this.pnlEditResourceEntry.ResumeLayout(false); + this.gbOriginalText.ResumeLayout(false); + this.gbOriginalText.PerformLayout(); + this.gbTranslatedText.ResumeLayout(false); + this.gbTranslatedText.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private UserControls.OkCancelButtons okCancelButtons; + private System.Windows.Forms.GroupBox gbResourceEntries; + private System.Windows.Forms.DataGridView gridResources; + private System.Windows.Forms.TableLayoutPanel pnlEditResourceEntry; + private System.Windows.Forms.GroupBox gbOriginalText; + private System.Windows.Forms.GroupBox gbTranslatedText; + private System.Windows.Forms.Splitter splitterEditResources; + private System.Windows.Forms.TextBox txtOriginalText; + private System.Windows.Forms.TextBox txtTranslatedText; + private System.Windows.Forms.BindingSource resourceEntryBindingSource; + private System.Windows.Forms.GroupBox gbResourceFile; + private System.Windows.Forms.ComboBox cmbResourceFiles; + private System.Windows.Forms.DataGridViewTextBoxColumn colResourceKey; + private System.Windows.Forms.DataGridViewTextBoxColumn colOriginalText; + private System.Windows.Forms.DataGridViewTextBoxColumn colTranslatedText; + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs new file mode 100644 index 0000000..f30601a --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs @@ -0,0 +1,109 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: EditResourcesForm.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System.Windows.Forms; + +using KGySoft.Drawing.ImagingTools.Model; +using KGySoft.Drawing.ImagingTools.ViewModel; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View.Forms +{ + internal partial class EditResourcesForm : MvvmBaseForm + { + #region Constructors + + #region Internal Constructors + + internal EditResourcesForm(EditResourcesViewModel viewModel) : base(viewModel) + { + InitializeComponent(); + } + + #endregion + + #region Private Constructors + + private EditResourcesForm() : this(null!) + { + // this ctor is just for the designer + } + + #endregion + + #endregion + + #region Methods + + #region Protected Methods + + protected override void ApplyResources() + { + base.ApplyResources(); + Icon = Properties.Resources.Language; + } + + protected override void ApplyViewModel() + { + InitCommandBindings(); + InitPropertyBindings(); + base.ApplyViewModel(); + } + + #endregion + + #region Private Methods + + private void InitPropertyBindings() + { + // VM.TitleCaption -> Text + CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.TitleCaption), this, nameof(Text)); + + // VM.ResourceFiles -> cmbResourceFiles.DataSource + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.ResourceFiles), nameof(cmbResourceFiles.DataSource), cmbResourceFiles); + + // VM.SelectedFile -> cmbResourceFiles.SelectedItem (cannot use two-way for SelectedItem because there is no SelectedItemChanged event) + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.SelectedFile), nameof(cmbResourceFiles.SelectedItem), cmbResourceFiles); + + // cmbResourceFiles.SelectedValue -> VM.SelectedFile (cannot use two-way for SelectedValue because ValueMember is not set) + CommandBindings.AddPropertyBinding(cmbResourceFiles, nameof(cmbResourceFiles.SelectedValue), nameof(ViewModel.SelectedFile), ViewModel); + + // VM.SelectedSet -> resourceEntryBindingSource.DataSource + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.SelectedSet), nameof(resourceEntryBindingSource.DataSource), resourceEntryBindingSource); + + // resourceEntryBindingSource.OriginalText -> txtOriginalText.Text + txtOriginalText.DataBindings.Add(nameof(txtOriginalText.Text), resourceEntryBindingSource, nameof(ResourceEntry.OriginalText), false, DataSourceUpdateMode.Never); + + // resourceEntryBindingSource.TranslatedText <-> txtTranslatedText.Text + txtTranslatedText.DataBindings.Add(nameof(txtTranslatedText.Text), resourceEntryBindingSource, nameof(ResourceEntry.TranslatedText), false, DataSourceUpdateMode.OnValidation); + } + + private void InitCommandBindings() + { + // OKButton.Click -> ViewModel.SaveResourcesCommand, and preventing closing the form if the command has executed with errors + CommandBindings.Add(ViewModel.SaveResourcesCommand, ViewModel.SaveResourcesCommandState) + .AddSource(okCancelButtons.OKButton, nameof(okCancelButtons.OKButton.Click)) + .Executed += (_, args) => DialogResult = args.State[EditResourcesViewModel.CommandStateExecutedWithError] is true ? DialogResult.None : DialogResult.OK; + } + + #endregion + + #endregion + } +} diff --git a/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs b/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs index dbe6635..24f3a4f 100644 --- a/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs +++ b/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs @@ -69,6 +69,7 @@ public static IView CreateView(IViewModel viewModel) AdjustContrastViewModel adjustContrastViewModel => new AdjustContrastForm(adjustContrastViewModel), AdjustGammaViewModel adjustGammaViewModel => new AdjustGammaForm(adjustGammaViewModel), LanguageSettingsViewModel languageSettingsViewModel => new LanguageSettingsForm(languageSettingsViewModel), + EditResourcesViewModel editResourcesViewModel => new EditResourcesForm(editResourcesViewModel), _ => throw new InvalidOperationException(Res.InternalError($"Unexpected viewModel type: {viewModel.GetType()}")) }; } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs new file mode 100644 index 0000000..6078a31 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -0,0 +1,243 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: EditResourcesViewModel.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using KGySoft.Resources; + +#region Used Namespaces + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Resources; + +using KGySoft.Collections; +using KGySoft.ComponentModel; +using KGySoft.CoreLibraries; +using KGySoft.Drawing.ImagingTools.Model; +using KGySoft.Reflection; + +#endregion + +#region Used Aliases + +using ResXResourceSet = KGySoft.Resources.ResXResourceSet; + +#endregion + +#endregion + +namespace KGySoft.Drawing.ImagingTools.ViewModel +{ + internal class EditResourcesViewModel : ViewModelBase + { + #region Constants + + internal const string CommandStateExecutedWithError = nameof(CommandStateExecutedWithError); + + #endregion + + #region Fields + + private readonly CultureInfo culture; + private readonly StringKeyedDictionary> resources = new(3); + + #endregion + + #region Properties + + internal string TitleCaption { get => Get(); set => Set(value); } + internal string[] ResourceFiles { get => Get(); set => Set(value); } + internal string SelectedFile { get => Get(); set => Set(value); } + internal IList SelectedSet { get => Get>(); set => Set(value); } + + internal ICommand SaveResourcesCommand => Get(() => new SimpleCommand(OnSaveResourcesCommand)); + internal ICommandState SaveResourcesCommandState => Get(() => new CommandState()); + + #endregion + + #region Constructors + + internal EditResourcesViewModel(CultureInfo culture) + { + this.culture = culture ?? throw new ArgumentNullException(nameof(culture), PublicResources.ArgumentNull); + ResourceFiles = new[] { ResHelper.CoreLibrariesBaseName, ResHelper.DrawingLibrariesBaseName, ResHelper.DrawingToolsBaseName }; + ResetTitle(); + SelectedFile = ResHelper.DrawingToolsBaseName; + } + + #endregion + + #region Methods + + #region Protected Methods + + protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) + { + base.OnPropertyChanged(e); + switch (e.PropertyName) + { + case nameof(SelectedFile): + UpdateSelectedResources((string)e.NewValue!); + break; + } + } + + protected override void Dispose(bool disposing) + { + if (IsDisposed) + return; + resources.Values.ForEach(set => (set as IDisposable)?.Dispose()); + base.Dispose(disposing); + } + + #endregion + + #region Private Methods + + private void ResetTitle() => TitleCaption = Res.TitleEditResources($"{culture.EnglishName} ({culture.NativeName})"); + + private string ToFileName(string baseName) => $"{baseName}.{culture.Name}.resx"; + + private string ToFileNameWithPath(string baseName) => Path.Combine(Res.ResourcesDir, ToFileName(baseName)); + + private void UpdateSelectedResources(string baseName) + { + IList? set = resources.GetValueOrDefault(baseName); + if (set != null) + { + SelectedSet = set; + return; + } + + if (!TryReadResources(baseName, out set, out Exception? error)) + { + if (!Confirm(Res.ConfirmMessageTryRegenerateResource(ToFileName(baseName), error.Message))) + { + SelectedSet = Reflector.EmptyArray(); + return; + } + + try + { + File.Delete(ToFileNameWithPath(baseName)); + } + catch (Exception e) when (!e.IsCritical()) + { + ShowError(Res.ErrorMessageFailedToRegenerateResource(ToFileName(baseName), error.Message)); + SelectedSet = Reflector.EmptyArray(); + return; + } + + if (!TryReadResources(baseName, out set, out error)) + { + ShowError(Res.ErrorMessageFailedToRegenerateResource(ToFileName(baseName), error.Message)); + SelectedSet = Reflector.EmptyArray(); + return; + } + } + + resources[baseName] = set; + SelectedSet = set; + } + + private bool TryReadResources(string baseName, [MaybeNullWhen(false)]out IList set, [MaybeNullWhen(true)]out Exception error) + { + try + { + // Creating a local resource manager so we can generate the entries that currently found in the compiled resource set. + // Auto appending only the queried keys so we can add the missing ones since the last creation and also remove the possibly removed ones. + // Note that this will not generate any .resx files as we use the default AutoSave = None + using var resourceManger = new DynamicResourceManager(baseName, ResHelper.GetAssembly(baseName)) + { + SafeMode = true, + Source = ResourceManagerSources.CompiledOnly, + AutoAppend = AutoAppendOptions.AppendFirstNeutralCulture, + }; + + ResourceSet compiled = resourceManger.GetResourceSet(CultureInfo.InvariantCulture, true, false)!; + resourceManger.Source = ResourceManagerSources.CompiledAndResX; + + // Note: this way we add even possibly missing entries that were added to compiled resources since last creation while removed entries will be skipped + var result = new SortableBindingList(); + foreach (DictionaryEntry entry in compiled) + result.Add(new ResourceEntry((string)entry.Key, (string)entry.Value, resourceManger.GetString((string)entry.Key, culture) ?? LanguageSettings.UntranslatedResourcePrefix + entry.Value)); + + result.ApplySort(nameof(ResourceEntry.Key), ListSortDirection.Ascending); + error = null; + set = result; + return true; + } + catch (Exception e) when (!e.IsCritical()) + { + error = e; + set = null; + return false; + } + } + + private bool TrySaveResources(string baseName, IList set, [MaybeNullWhen(true)]out Exception error) + { + // Note: We do not use a DynamicResourceManager for saving. This works because we let the actual DRMs drop their content after saving. + try + { + using var resx = new ResXResourceSet(); + foreach (ResourceEntry res in set) + resx.SetObject(res.Key, res.TranslatedText); + + resx.Save(ToFileNameWithPath(baseName)); + error = null; + return true; + } + catch (Exception e) when (!e.IsCritical()) + { + error = e; + return false; + } + } + + #endregion + + #region Command Handlers + + private void OnSaveResourcesCommand(ICommandState state) + { + foreach (KeyValuePair> set in resources) + { + if (set.Value.Count == 0) + continue; + if (TrySaveResources(set.Key, set.Value, out Exception? error)) + continue; + + ShowError(Res.ErrorMessageFailedToSaveResource(ToFileName(set.Key), error.Message)); + state[CommandStateExecutedWithError] = true; + return; + } + + state[CommandStateExecutedWithError] = false; + ResHelper.ReleaseAllResources(); + } + + #endregion + + #endregion + } +} diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index e0eb0f0..ae49b34 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -142,7 +142,7 @@ private void ResetLanguages() return; } - var result = new SortableBindingList(ExistingLanguagesOnly ? Res.GetAvailableLanguages() : NeutralLanguages); + var result = new SortableBindingList(ExistingLanguagesOnly ? ResHelper.GetAvailableLanguages() : NeutralLanguages); result.ApplySort(nameof(CultureInfo.EnglishName), ListSortDirection.Ascending); CultureInfo lastSelectedLanguage = CurrentLanguage; Languages = result; @@ -156,7 +156,13 @@ private void ResetLanguages() private void OnApplyCommand() { LanguageSettings.DynamicResourceManagersSource = AllowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; - LanguageSettings.DisplayLanguage = CurrentLanguage; + LanguageSettings.DisplayLanguage = activeLanguage = CurrentLanguage; + + // Note: Ensure is not really needed because main .resx is generated, while others are saved on demand in the editor, too + // TODO If used, then add to EditResourcesVM.Save, too to be consistent + //ResHelper.EnsureResourcesGenerated(); + ResHelper.SavePendingResources(); + ApplyCommandState.Enabled = false; } @@ -184,8 +190,8 @@ private void OnSaveConfigCommand() private void OnEditResourcesCommand() { - //using IViewModel viewModel = ViewModelFactory.CreateEditResources(CurrentLanguage); - //ShowChildViewCallback?.Invoke(viewModel); + using IViewModel viewModel = ViewModelFactory.CreateEditResources(CurrentLanguage); + ShowChildViewCallback?.Invoke(viewModel); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs index 4e0773b..d8dc559 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs @@ -227,11 +227,11 @@ public static IViewModel FromGraphics(Graphics? graphics) /// An instance that represents a view model for managing language settings. public static IViewModel CreateLanguageSettings() => new LanguageSettingsViewModel(); - ///// - ///// Creates a view model for managing language settings. - ///// - ///// An instance that represents a view model for managing language settings. - //public static IViewModel CreateEditResources(CultureInfo culture) => new EditResourcesViewModel(culture); + /// + /// Creates a view model for managing language settings. + /// + /// An instance that represents a view model for managing language settings. + public static IViewModel CreateEditResources(CultureInfo culture) => new EditResourcesViewModel(culture); #endregion } diff --git a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs new file mode 100644 index 0000000..f4ed7ba --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs @@ -0,0 +1,134 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: ResHelper.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Reflection; + +using KGySoft.Collections; +using KGySoft.CoreLibraries; +using KGySoft.Reflection; +using KGySoft.Resources; + +#endregion + +namespace KGySoft.Drawing.ImagingTools +{ + internal static class ResHelper + { + #region Constants + + internal const string CoreLibrariesBaseName = "KGySoft.CoreLibraries.Messages"; + internal const string DrawingLibrariesBaseName = "KGySoft.Drawing.Messages"; + internal const string DrawingToolsBaseName = "KGySoft.Drawing.ImagingTools.Messages"; + + #endregion + + #region Fields + + private static StringKeyedDictionary? culturesCache; + private static DynamicResourceManager[]? knownResourceManagers; + + #endregion + + #region Properties + + private static StringKeyedDictionary CulturesCache + => culturesCache ??= CultureInfo.GetCultures(CultureTypes.AllCultures).ToStringKeyedDictionary(ci => ci.Name); + + private static DynamicResourceManager[] KnownResourceManagers + => knownResourceManagers ??= new[] + { + (DynamicResourceManager)Reflector.GetField(Reflector.ResolveType(typeof(LanguageSettings).Assembly, "KGySoft.Res")!, "resourceManager")!, + (DynamicResourceManager)Reflector.GetField(Reflector.ResolveType(typeof(DrawingModule).Assembly, "KGySoft.Res")!, "resourceManager")!, + (DynamicResourceManager)Reflector.GetField(typeof(Res), "resourceManager")! + }; + + #endregion + + #region Methods + + internal static IList GetAvailableLanguages() + { + string dir = Res.ResourcesDir; + try + { + var result = new List { Res.DefaultLanguage }; + if (!Directory.Exists(dir)) + return result; + + int startIndex = dir.Length + DrawingToolsBaseName.Length + 2; + string[] files = Directory.GetFiles(dir, $"{DrawingToolsBaseName}.*.resx", SearchOption.TopDirectoryOnly); + foreach (string file in files) + { + StringSegment resName = file.AsSegment(startIndex, file.Length - startIndex - 5); + if (CulturesCache.TryGetValue(resName, out CultureInfo? ci) && !ci.In(CultureInfo.InvariantCulture, Res.DefaultLanguage)) + result.Add(ci); + } + + return result; + } + catch (Exception e) when (!e.IsCritical()) + { + return new[] { Res.DefaultLanguage }; + } + } + + internal static Assembly GetAssembly(string baseName) => baseName switch + { + CoreLibrariesBaseName => typeof(LanguageSettings).Assembly, + DrawingLibrariesBaseName => typeof(DrawingModule).Assembly, + DrawingToolsBaseName => typeof(Res).Assembly, + _ => throw new InvalidOperationException(Res.InternalError($"Unexpected baseName: {baseName}")) + }; + + /// + /// Generates possible missing resources for the current language in memory so next Save will persist them. + /// TODO: Remove when LanguageSettings.EnsureResourcesGenerated will be available. + /// + internal static void EnsureResourcesGenerated() + { + foreach (DynamicResourceManager resourceManager in KnownResourceManagers) + resourceManager.GetExpandoResourceSet(LanguageSettings.DisplayLanguage, ResourceSetRetrieval.CreateIfNotExists, true); + } + + /// + /// Saves the pending resources of centralized DRMs. + /// TODO: Remove when LanguageSettings.SavePendingResources will be available. + /// + internal static void SavePendingResources() + { + foreach (DynamicResourceManager resourceManager in KnownResourceManagers) + resourceManager.SaveAllResources(); + } + + /// + /// Releases all resource sets without saving of centralized DRMs. + /// TODO: Remove when LanguageSettings.ReleaseAllResources will be available. + /// + internal static void ReleaseAllResources() + { + foreach (DynamicResourceManager resourceManager in KnownResourceManagers) + resourceManager.ReleaseAllResources(); + } + + #endregion + } +} diff --git a/changelog.txt b/changelog.txt index ffecc9f..10cc4ce 100644 --- a/changelog.txt +++ b/changelog.txt @@ -22,6 +22,7 @@ * Members are annotated for using C# 8.0 nullable references + ViewModelFactory class: + New CreateLanguageSettings method + + New CreateEditResources method - KGySoft.Drawing.DebuggerVisualizers.dll ========================================= From adc70f21ec20d7e3241d4be8178d61e12062428d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 21 May 2021 20:03:51 +0200 Subject: [PATCH 036/211] Moving IsModified up to parent interface --- .../ViewModel/_Interfaces/IViewModel.cs | 8 ++++++++ .../ViewModel/_Interfaces/IViewModel`1.cs | 11 +---------- changelog.txt | 4 ++++ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/_Interfaces/IViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/_Interfaces/IViewModel.cs index aa824b6..1905c6c 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/_Interfaces/IViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/_Interfaces/IViewModel.cs @@ -28,5 +28,13 @@ namespace KGySoft.Drawing.ImagingTools.ViewModel /// public interface IViewModel : IDisposable { + #region Properties + + /// + /// Gets whether the model instance that belongs this view model instance is modified. + /// + bool IsModified { get; } + + #endregion } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/_Interfaces/IViewModel`1.cs b/KGySoft.Drawing.ImagingTools/ViewModel/_Interfaces/IViewModel`1.cs index a7d0cfe..b3a3310 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/_Interfaces/IViewModel`1.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/_Interfaces/IViewModel`1.cs @@ -24,19 +24,10 @@ namespace KGySoft.Drawing.ImagingTools.ViewModel /// public interface IViewModel : IViewModel { - #region Properties - - /// - /// Gets whether the model instance that belongs this view model instance is modified. - /// - bool IsModified { get; } - - #endregion - #region Methods /// - /// If returns , then this method returns the edited model. + /// If returns , then this method returns the edited model. /// /// The edited model. TModel GetEditedModel(); diff --git a/changelog.txt b/changelog.txt index 10cc4ce..e756f0d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -23,6 +23,10 @@ + ViewModelFactory class: + New CreateLanguageSettings method + New CreateEditResources method + + IViewModel interface + + New IsModified property + * IViewModel interface + * The IsModified property is now inherited from IViewModel - KGySoft.Drawing.DebuggerVisualizers.dll ========================================= From 90ab2510b7a8fa5484a9116b913f60cda8878f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 21 May 2021 20:04:16 +0200 Subject: [PATCH 037/211] Refactoring language settings and editing --- .../View/Forms/EditResourcesForm.cs | 25 +++--- .../View/Forms/LanguageSettingsForm.cs | 2 + .../ViewModel/EditResourcesViewModel.cs | 81 +++++++++++-------- .../ViewModel/LanguageSettingsViewModel.cs | 20 +++-- .../_Classes/ResHelper.cs | 33 +++++--- .../_Enums/ResourceOwner.cs | 25 ++++++ 6 files changed, 127 insertions(+), 59 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/_Enums/ResourceOwner.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs index f30601a..6cd7f06 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs @@ -16,6 +16,7 @@ #region Usings +using System.Collections.Generic; using System.Windows.Forms; using KGySoft.Drawing.ImagingTools.Model; @@ -33,7 +34,10 @@ internal partial class EditResourcesForm : MvvmBaseForm internal EditResourcesForm(EditResourcesViewModel viewModel) : base(viewModel) { + // Note: Not setting Accept/CancelButton because they would be very annoying during the editing InitializeComponent(); + cmbResourceFiles.ValueMember = nameof(KeyValuePair.Key); + cmbResourceFiles.DisplayMember = nameof(KeyValuePair.Value); } #endregion @@ -72,17 +76,14 @@ protected override void ApplyViewModel() private void InitPropertyBindings() { - // VM.TitleCaption -> Text - CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.TitleCaption), this, nameof(Text)); - // VM.ResourceFiles -> cmbResourceFiles.DataSource - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.ResourceFiles), nameof(cmbResourceFiles.DataSource), cmbResourceFiles); + cmbResourceFiles.DataSource = ViewModel.ResourceFiles; - // VM.SelectedFile -> cmbResourceFiles.SelectedItem (cannot use two-way for SelectedItem because there is no SelectedItemChanged event) - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.SelectedFile), nameof(cmbResourceFiles.SelectedItem), cmbResourceFiles); + // VM.TitleCaption -> Text + CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.TitleCaption), this, nameof(Text)); - // cmbResourceFiles.SelectedValue -> VM.SelectedFile (cannot use two-way for SelectedValue because ValueMember is not set) - CommandBindings.AddPropertyBinding(cmbResourceFiles, nameof(cmbResourceFiles.SelectedValue), nameof(ViewModel.SelectedFile), ViewModel); + // VM.SelectedLibrary <-> cmbResourceFiles.SelectedValue + CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.SelectedLibrary), cmbResourceFiles, nameof(cmbResourceFiles.SelectedValue)); // VM.SelectedSet -> resourceEntryBindingSource.DataSource CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.SelectedSet), nameof(resourceEntryBindingSource.DataSource), resourceEntryBindingSource); @@ -97,9 +98,13 @@ private void InitPropertyBindings() private void InitCommandBindings() { // OKButton.Click -> ViewModel.SaveResourcesCommand, and preventing closing the form if the command has executed with errors - CommandBindings.Add(ViewModel.SaveResourcesCommand, ViewModel.SaveResourcesCommandState) + CommandBindings.Add(ViewModel.SaveResourcesCommand) .AddSource(okCancelButtons.OKButton, nameof(okCancelButtons.OKButton.Click)) - .Executed += (_, args) => DialogResult = args.State[EditResourcesViewModel.CommandStateExecutedWithError] is true ? DialogResult.None : DialogResult.OK; + .Executed += (_, args) => DialogResult = args.State[EditResourcesViewModel.StateSaveExecutedWithError] is true ? DialogResult.None : DialogResult.OK; + + // CancelButton.Click -> ViewModel.CancelResourcesCommand + CommandBindings.Add(ViewModel.CancelEditCommand) + .AddSource(okCancelButtons.CancelButton, nameof(okCancelButtons.CancelButton.Click)); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs index fbb2621..8bc92ac 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs @@ -35,6 +35,8 @@ internal partial class LanguageSettingsForm : MvvmBaseForm> resources = new(3); + private readonly Dictionary ResourceSet, bool IsModified)> resources; #endregion #region Properties + internal KeyValuePair[] ResourceFiles { get; } // get only because never changes internal string TitleCaption { get => Get(); set => Set(value); } - internal string[] ResourceFiles { get => Get(); set => Set(value); } - internal string SelectedFile { get => Get(); set => Set(value); } + internal ResourceOwner SelectedLibrary { get => Get(); set => Set(value); } internal IList SelectedSet { get => Get>(); set => Set(value); } internal ICommand SaveResourcesCommand => Get(() => new SimpleCommand(OnSaveResourcesCommand)); - internal ICommandState SaveResourcesCommandState => Get(() => new CommandState()); + internal ICommand CancelEditCommand => Get(() => new SimpleCommand(OnCancelEditCommand)); #endregion @@ -79,9 +79,10 @@ internal class EditResourcesViewModel : ViewModelBase internal EditResourcesViewModel(CultureInfo culture) { this.culture = culture ?? throw new ArgumentNullException(nameof(culture), PublicResources.ArgumentNull); - ResourceFiles = new[] { ResHelper.CoreLibrariesBaseName, ResHelper.DrawingLibrariesBaseName, ResHelper.DrawingToolsBaseName }; + resources = new Dictionary, bool)>(3, EnumComparer.Comparer); + ResourceFiles = Enum.GetValues().Select(o => new KeyValuePair(o, ToFileName(o))).ToArray(); ResetTitle(); - SelectedFile = ResHelper.DrawingToolsBaseName; + SelectedLibrary = ResourceOwner.DrawingTools; } #endregion @@ -90,13 +91,15 @@ internal EditResourcesViewModel(CultureInfo culture) #region Protected Methods + protected override bool AffectsModifiedState(string propertyName) => false; // set explicitly + protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) { base.OnPropertyChanged(e); switch (e.PropertyName) { - case nameof(SelectedFile): - UpdateSelectedResources((string)e.NewValue!); + case nameof(SelectedLibrary): + UpdateSelectedResources((ResourceOwner)e.NewValue!); break; } } @@ -105,7 +108,7 @@ protected override void Dispose(bool disposing) { if (IsDisposed) return; - resources.Values.ForEach(set => (set as IDisposable)?.Dispose()); + resources.Values.ForEach(v => (v.ResourceSet as IDisposable)?.Dispose()); base.Dispose(disposing); } @@ -115,22 +118,21 @@ protected override void Dispose(bool disposing) private void ResetTitle() => TitleCaption = Res.TitleEditResources($"{culture.EnglishName} ({culture.NativeName})"); - private string ToFileName(string baseName) => $"{baseName}.{culture.Name}.resx"; + private string ToFileName(ResourceOwner owner) => $"{ResHelper.GetBaseName(owner)}.{culture.Name}.resx"; - private string ToFileNameWithPath(string baseName) => Path.Combine(Res.ResourcesDir, ToFileName(baseName)); + private string ToFileNameWithPath(ResourceOwner owner) => Path.Combine(Res.ResourcesDir, ToFileName(owner)); - private void UpdateSelectedResources(string baseName) + private void UpdateSelectedResources(ResourceOwner owner) { - IList? set = resources.GetValueOrDefault(baseName); - if (set != null) + if (resources.TryGetValue(owner, out var value)) { - SelectedSet = set; + SelectedSet = value.ResourceSet; return; } - if (!TryReadResources(baseName, out set, out Exception? error)) + if (!TryReadResources(owner, out IList? set, out Exception? error)) { - if (!Confirm(Res.ConfirmMessageTryRegenerateResource(ToFileName(baseName), error.Message))) + if (!Confirm(Res.ConfirmMessageTryRegenerateResource(ToFileName(owner), error.Message))) { SelectedSet = Reflector.EmptyArray(); return; @@ -138,35 +140,35 @@ private void UpdateSelectedResources(string baseName) try { - File.Delete(ToFileNameWithPath(baseName)); + File.Delete(ToFileNameWithPath(owner)); } catch (Exception e) when (!e.IsCritical()) { - ShowError(Res.ErrorMessageFailedToRegenerateResource(ToFileName(baseName), error.Message)); + ShowError(Res.ErrorMessageFailedToRegenerateResource(ToFileName(owner), error.Message)); SelectedSet = Reflector.EmptyArray(); return; } - if (!TryReadResources(baseName, out set, out error)) + if (!TryReadResources(owner, out set, out error)) { - ShowError(Res.ErrorMessageFailedToRegenerateResource(ToFileName(baseName), error.Message)); + ShowError(Res.ErrorMessageFailedToRegenerateResource(ToFileName(owner), error.Message)); SelectedSet = Reflector.EmptyArray(); return; } } - resources[baseName] = set; + resources[owner] = (set, false); SelectedSet = set; } - private bool TryReadResources(string baseName, [MaybeNullWhen(false)]out IList set, [MaybeNullWhen(true)]out Exception error) + private bool TryReadResources(ResourceOwner owner, [MaybeNullWhen(false)]out IList set, [MaybeNullWhen(true)]out Exception error) { try { // Creating a local resource manager so we can generate the entries that currently found in the compiled resource set. // Auto appending only the queried keys so we can add the missing ones since the last creation and also remove the possibly removed ones. // Note that this will not generate any .resx files as we use the default AutoSave = None - using var resourceManger = new DynamicResourceManager(baseName, ResHelper.GetAssembly(baseName)) + using var resourceManger = new DynamicResourceManager(ResHelper.GetBaseName(owner), ResHelper.GetAssembly(owner)) { SafeMode = true, Source = ResourceManagerSources.CompiledOnly, @@ -181,6 +183,17 @@ private bool TryReadResources(string baseName, [MaybeNullWhen(false)]out IList + { + if (args.ListChangedType != ListChangedType.ItemChanged) + return; + + if (!resources.TryGetValue(owner, out var value) || value.IsModified) + return; + + resources[owner] = (value.ResourceSet, true); + SetModified(true); + }; result.ApplySort(nameof(ResourceEntry.Key), ListSortDirection.Ascending); error = null; set = result; @@ -194,7 +207,7 @@ private bool TryReadResources(string baseName, [MaybeNullWhen(false)]out IList set, [MaybeNullWhen(true)]out Exception error) + private bool TrySaveResources(ResourceOwner owner, IList set, [MaybeNullWhen(true)]out Exception error) { // Note: We do not use a DynamicResourceManager for saving. This works because we let the actual DRMs drop their content after saving. try @@ -203,7 +216,7 @@ private bool TrySaveResources(string baseName, IList set, [MaybeN foreach (ResourceEntry res in set) resx.SetObject(res.Key, res.TranslatedText); - resx.Save(ToFileNameWithPath(baseName)); + resx.Save(ToFileNameWithPath(owner)); error = null; return true; } @@ -220,22 +233,24 @@ private bool TrySaveResources(string baseName, IList set, [MaybeN private void OnSaveResourcesCommand(ICommandState state) { - foreach (KeyValuePair> set in resources) + foreach (var set in resources) { - if (set.Value.Count == 0) + if (!set.Value.IsModified) continue; - if (TrySaveResources(set.Key, set.Value, out Exception? error)) + if (TrySaveResources(set.Key, set.Value.ResourceSet, out Exception? error)) continue; ShowError(Res.ErrorMessageFailedToSaveResource(ToFileName(set.Key), error.Message)); - state[CommandStateExecutedWithError] = true; + state[StateSaveExecutedWithError] = true; return; } - state[CommandStateExecutedWithError] = false; + state[StateSaveExecutedWithError] = false; ResHelper.ReleaseAllResources(); } + private void OnCancelEditCommand() => SetModified(false); + #endregion #endregion diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index ae49b34..016f1a5 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -35,7 +35,6 @@ internal class LanguageSettingsViewModel : ViewModelBase private readonly bool initializing; - private CultureInfo activeLanguage; private CultureInfo[]? neutralLanguages; #endregion @@ -72,7 +71,7 @@ internal class LanguageSettingsViewModel : ViewModelBase internal LanguageSettingsViewModel() { initializing = true; - CurrentLanguage = activeLanguage = LanguageSettings.DisplayLanguage; + CurrentLanguage = LanguageSettings.DisplayLanguage; AllowResXResources = Settings.Default.AllowResXResources; UseOSLanguage = Settings.Default.UseOSLanguage; ExistingLanguagesOnly = true; // could be the default value but this way we spare one reset when initializing binding @@ -105,7 +104,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) break; case nameof(CurrentLanguage): - ApplyCommandState.Enabled = !Equals(e.NewValue, activeLanguage); + ApplyCommandState.Enabled = !Equals(e.NewValue, LanguageSettings.DisplayLanguage); EditResourcesCommandState.Enabled = !Equals(e.NewValue, Res.DefaultLanguage); break; } @@ -155,11 +154,16 @@ private void ResetLanguages() private void OnApplyCommand() { + CultureInfo currentLanguage = CurrentLanguage; LanguageSettings.DynamicResourceManagersSource = AllowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; - LanguageSettings.DisplayLanguage = activeLanguage = CurrentLanguage; + + if (Equals(LanguageSettings.DisplayLanguage, currentLanguage)) + ResHelper.RaiseLanguageChanged(); + else + LanguageSettings.DisplayLanguage = currentLanguage; // Note: Ensure is not really needed because main .resx is generated, while others are saved on demand in the editor, too - // TODO If used, then add to EditResourcesVM.Save, too to be consistent + // TODO If used, then add to EditResourcesVM.Save, too, to be consistent () //ResHelper.EnsureResourcesGenerated(); ResHelper.SavePendingResources(); @@ -177,7 +181,7 @@ private void OnSaveConfigCommand() // saving the configuration Settings.Default.AllowResXResources = AllowResXResources; Settings.Default.UseOSLanguage = UseOSLanguage; - Settings.Default.DisplayLanguage = activeLanguage = CurrentLanguage; + Settings.Default.DisplayLanguage = CurrentLanguage; try { Settings.Default.Save(); @@ -192,6 +196,10 @@ private void OnEditResourcesCommand() { using IViewModel viewModel = ViewModelFactory.CreateEditResources(CurrentLanguage); ShowChildViewCallback?.Invoke(viewModel); + + // If the language was edited, then enabling apply even if it was disabled + if (viewModel.IsModified) + ApplyCommandState.Enabled = true; } #endregion diff --git a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs index f4ed7ba..f5523fe 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs @@ -35,9 +35,9 @@ internal static class ResHelper { #region Constants - internal const string CoreLibrariesBaseName = "KGySoft.CoreLibraries.Messages"; - internal const string DrawingLibrariesBaseName = "KGySoft.Drawing.Messages"; - internal const string DrawingToolsBaseName = "KGySoft.Drawing.ImagingTools.Messages"; + private const string coreLibrariesBaseName = "KGySoft.CoreLibraries.Messages"; + private const string drawingLibrariesBaseName = "KGySoft.Drawing.Messages"; + private const string drawingToolsBaseName = "KGySoft.Drawing.ImagingTools.Messages"; #endregion @@ -74,8 +74,8 @@ internal static IList GetAvailableLanguages() if (!Directory.Exists(dir)) return result; - int startIndex = dir.Length + DrawingToolsBaseName.Length + 2; - string[] files = Directory.GetFiles(dir, $"{DrawingToolsBaseName}.*.resx", SearchOption.TopDirectoryOnly); + int startIndex = dir.Length + drawingToolsBaseName.Length + 2; + string[] files = Directory.GetFiles(dir, $"{drawingToolsBaseName}.*.resx", SearchOption.TopDirectoryOnly); foreach (string file in files) { StringSegment resName = file.AsSegment(startIndex, file.Length - startIndex - 5); @@ -91,12 +91,20 @@ internal static IList GetAvailableLanguages() } } - internal static Assembly GetAssembly(string baseName) => baseName switch + internal static string GetBaseName(ResourceOwner owner) => owner switch { - CoreLibrariesBaseName => typeof(LanguageSettings).Assembly, - DrawingLibrariesBaseName => typeof(DrawingModule).Assembly, - DrawingToolsBaseName => typeof(Res).Assembly, - _ => throw new InvalidOperationException(Res.InternalError($"Unexpected baseName: {baseName}")) + ResourceOwner.CoreLibraries => coreLibrariesBaseName, + ResourceOwner.DrawingLibraries => drawingLibrariesBaseName, + ResourceOwner.DrawingTools => drawingToolsBaseName, + _ => throw new ArgumentOutOfRangeException(nameof(owner), PublicResources.EnumOutOfRange(owner)) + }; + + internal static Assembly GetAssembly(ResourceOwner owner) => owner switch + { + ResourceOwner.CoreLibraries => typeof(LanguageSettings).Assembly, + ResourceOwner.DrawingLibraries => typeof(DrawingModule).Assembly, + ResourceOwner.DrawingTools => typeof(Res).Assembly, + _ => throw new ArgumentOutOfRangeException(nameof(owner), PublicResources.EnumOutOfRange(owner)) }; /// @@ -129,6 +137,11 @@ internal static void ReleaseAllResources() resourceManager.ReleaseAllResources(); } + /// + /// TODO: Remove when LanguageSettings.RaiseLanguageChanged will be available. + /// + internal static void RaiseLanguageChanged() => Reflector.InvokeMethod(typeof(LanguageSettings), "OnDisplayLanguageChanged", EventArgs.Empty); + #endregion } } diff --git a/KGySoft.Drawing.ImagingTools/_Enums/ResourceOwner.cs b/KGySoft.Drawing.ImagingTools/_Enums/ResourceOwner.cs new file mode 100644 index 0000000..9327601 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/_Enums/ResourceOwner.cs @@ -0,0 +1,25 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: ResourceOwner.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +namespace KGySoft.Drawing.ImagingTools +{ + internal enum ResourceOwner + { + CoreLibraries, + DrawingLibraries, + DrawingTools + } +} \ No newline at end of file From 77c1fac04d332887b2eaab7fac214ad203ee47b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 22 May 2021 11:28:17 +0200 Subject: [PATCH 038/211] New resources, handling DataGridView columns --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 21 +++++++++++++++++++ .../View/_Extensions/ControlExtensions.cs | 5 +++++ 2 files changed, 26 insertions(+) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 061ef6a..be13e60 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -792,4 +792,25 @@ Newly generated resources will contain English texts prefixed with "[T]". You ca Edit Resources... + + Resource File + + + Resource Entries + + + Resource Key + + + Original Text + + + Translated Text + + + Original Text + + + Translated Text + \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs index 7c43558..ff27f6b 100644 --- a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs @@ -112,6 +112,11 @@ static void ApplyToolStripResources(ToolStripItemCollection items) ApplyToolStripResources(toolStrip.Items); break; + case DataGridView dataGridView: + foreach (DataGridViewColumn item in dataGridView.Columns) + Res.ApplyStringResources(item, item.Name); + break; + default: foreach (Control child in control.Controls) child.ApplyStringResources(toolTip); From 0f39d075fb759fdbe6592d160deab31cd496d395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 22 May 2021 12:53:43 +0200 Subject: [PATCH 039/211] Handling dynamic resources update on language change --- KGySoft.Drawing.ImagingTools/Res.cs | 34 ++++++------ .../View/Forms/CountColorsForm.Designer.cs | 24 ++++----- .../View/Forms/CountColorsForm.cs | 4 +- .../BitmapDataVisualizerViewModel.cs | 2 +- .../ViewModel/ColorVisualizerViewModel.cs | 11 ++++ .../ViewModel/CountColorsViewModel.cs | 25 +++++++-- .../ViewModel/EditResourcesViewModel.cs | 6 ++- .../ViewModel/ImageVisualizerViewModel.cs | 54 +++++++++++++------ .../ViewModel/ManageInstallationsViewModel.cs | 9 ++++ .../ViewModel/TransformBitmapViewModelBase.cs | 2 + .../ViewModel/ViewModelBase.cs | 23 ++++++++ 11 files changed, 140 insertions(+), 54 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index ce4e2b1..f85c43b 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -141,10 +141,13 @@ internal static class Res internal static string TextAuto => Get("Text_Auto"); /// Counting colors... - internal static string TextCountingColors => Get("Text_CountingColors"); + internal static string TextCountingColorsId => "Text_CountingColors"; /// Operation has been canceled. - internal static string TextOperationCanceled => Get("Text_OperationCanceled"); + internal static string TextOperationCanceledId => "Text_OperationCanceled"; + + /// Color Count: {0} + internal static string TextColorCountId => "Text_ColorCountFormat"; #endregion @@ -161,18 +164,21 @@ internal static class Res #region Notifications /// The loaded metafile has been converted to Bitmap. To load it as a Metafile, choose the Image Debugger Visualizer instead. - internal static string NotificationMetafileAsBitmap => Get("Notification_MetafileAsBitmap"); + internal static string NotificationMetafileAsBitmapId => "Notification_MetafileAsBitmap"; /// The loaded image has been converted to Icon - internal static string NotificationImageAsIcon => Get("Notification_ImageAsIcon"); + internal static string NotificationImageAsIconId => "Notification_ImageAsIcon"; /// The palette of an indexed BitmapData cannot be reconstructed, therefore a default palette is used. You can change palette colors in the menu. - internal static string NotificationPaletteCannotBeRestored => Get("Notification_PaletteCannotBeRestored"); + internal static string NotificationPaletteCannotBeRestoredId => "Notification_PaletteCannotBeRestored"; #endregion #region Messages + /// Error: {0} + internal static string ErrorMessageId => "ErrorMessageFormat"; + /// Saving modifications as animated GIF is not supported internal static string ErrorMessageAnimGifNotSupported => Get("ErrorMessage_AnimGifNotSupported"); @@ -271,6 +277,12 @@ internal static void EnsureInitialized() internal static string Get(string id) => GetStringOrNull(id) ?? String.Format(CultureInfo.InvariantCulture, unavailableResource, id); + internal static string Get(string id, params object?[]? args) + { + string format = Get(id); + return args == null ? format : SafeFormat(format, args); + } + internal static string Get(TEnum value) where TEnum : struct, Enum => Get($"{value.GetType().Name}.{Enum.ToString(value)}"); internal static void ApplyStringResources(object target, string name) @@ -337,9 +349,6 @@ internal static void ApplyStringResources(object target, string name) /// B: {0} internal static string TextBlueValue(byte a) => Get("Text_BlueValueFormat", a); - /// Color Count: {0} - internal static string TextColorCount(int a) => Get("Text_ColorCountFormat", a); - #endregion #region Info Texts @@ -413,9 +422,6 @@ internal static string InfoColor(int argb, string knownColors, string systemColo #region Messages - /// Error: {0} - internal static string ErrorMessage(string error) => Get("ErrorMessageFormat", error); - /// Could not load file due to an error: {0} internal static string ErrorMessageFailedToLoadFile(string error) => Get("ErrorMessage_FailedToLoadFileFormat", error); @@ -570,12 +576,6 @@ internal static string InfoColor(int argb, string knownColors, string systemColo #region Private Methods - private static string Get(string id, params object?[]? args) - { - string format = Get(id); - return args == null ? format : SafeFormat(format, args); - } - private static string SafeFormat(string format, object?[] args) { try diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.Designer.cs index 7cacc14..d9e46bb 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.Designer.cs @@ -15,23 +15,23 @@ partial class CountColorsForm /// private void InitializeComponent() { - this.lblResult = new System.Windows.Forms.Label(); + this.lblCountColorsStatus = new System.Windows.Forms.Label(); this.pnlButton = new System.Windows.Forms.Panel(); this.btnClose = new System.Windows.Forms.Button(); this.progress = new KGySoft.Drawing.ImagingTools.View.Controls.DrawingProgressStatusStrip(); this.pnlButton.SuspendLayout(); this.SuspendLayout(); // - // lblResult + // lblCountColorsStatus // - this.lblResult.Dock = System.Windows.Forms.DockStyle.Fill; - this.lblResult.Location = new System.Drawing.Point(0, 0); - this.lblResult.Name = "lblResult"; - this.lblResult.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); - this.lblResult.Size = new System.Drawing.Size(274, 39); - this.lblResult.TabIndex = 0; - this.lblResult.Text = "lblResult"; - this.lblResult.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.lblCountColorsStatus.Dock = System.Windows.Forms.DockStyle.Fill; + this.lblCountColorsStatus.Location = new System.Drawing.Point(0, 0); + this.lblCountColorsStatus.Name = "lblCountColorsStatus"; + this.lblCountColorsStatus.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); + this.lblCountColorsStatus.Size = new System.Drawing.Size(274, 39); + this.lblCountColorsStatus.TabIndex = 0; + this.lblCountColorsStatus.Text = "lblCountColorsStatus"; + this.lblCountColorsStatus.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; // // pnlButton // @@ -68,7 +68,7 @@ private void InitializeComponent() this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(274, 90); - this.Controls.Add(this.lblResult); + this.Controls.Add(this.lblCountColorsStatus); this.Controls.Add(this.pnlButton); this.Controls.Add(this.progress); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; @@ -85,7 +85,7 @@ private void InitializeComponent() #endregion private Controls.DrawingProgressStatusStrip progress; - private System.Windows.Forms.Label lblResult; + private System.Windows.Forms.Label lblCountColorsStatus; private System.Windows.Forms.Panel pnlButton; private System.Windows.Forms.Button btnClose; } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.cs index 7a61dc9..7f01d3e 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.cs @@ -92,8 +92,8 @@ private void InitCommandBindings() private void InitPropertyBindings() { - // VM.DisplayText <-> lblResult.Text - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.DisplayText), nameof(lblResult.Text), lblResult); + // VM.DisplayText <-> lblCountColorsStatus.Text + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.DisplayText), nameof(lblCountColorsStatus.Text), lblCountColorsStatus); // in lock because it is already running lock (ViewModel.ProgressSyncRoot) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/BitmapDataVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/BitmapDataVisualizerViewModel.cs index e890d11..2002bf8 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/BitmapDataVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/BitmapDataVisualizerViewModel.cs @@ -63,7 +63,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) var bitmapDataInfo = (BitmapDataInfo?)e.NewValue; Image = bitmapDataInfo?.BackingImage; if ((bitmapDataInfo?.BitmapData?.PixelFormat ?? PixelFormat.Format32bppArgb).ToBitsPerPixel() <= 8) - Notification = Res.NotificationPaletteCannotBeRestored; + SetNotification(Res.NotificationPaletteCannotBeRestoredId); } } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ColorVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ColorVisualizerViewModel.cs index b30a8d0..a191432 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ColorVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ColorVisualizerViewModel.cs @@ -17,6 +17,7 @@ #region Usings using System.Drawing; +using KGySoft.ComponentModel; #endregion @@ -33,8 +34,18 @@ internal class ColorVisualizerViewModel : ViewModelBase, IViewModel #region Methods + #region Public Methods + public Color GetEditedModel() => Color; #endregion + + #region Protected Methods + + protected override void ApplyDisplayLanguage() => OnPropertyChanged(new PropertyChangedExtendedEventArgs(Color, Color, nameof(Color))); + + #endregion + + #endregion } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs index b1cd136..da91db5 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs @@ -53,6 +53,8 @@ private sealed class CountTask : AsyncTaskBase private volatile CountTask? task; private int? colorCount; + private string displayTextId; + private object[]? displayTextArgs; #endregion @@ -61,7 +63,7 @@ private sealed class CountTask : AsyncTaskBase internal object ProgressSyncRoot => drawingProgressManager; internal bool IsProcessing { get => Get(); set => Set(value); } internal DrawingProgress Progress { get => Get(); set => Set(value); } - internal string DisplayText { get => Get(() => Res.TextCountingColors); set => Set(value); } + internal string DisplayText { get => Get(); set => Set(value); } internal ICommand CancelCommand => Get(() => new SimpleCommand(OnCancelCommand)); @@ -73,6 +75,7 @@ internal CountColorsViewModel(Bitmap bitmap) { if (bitmap == null) throw new ArgumentNullException(nameof(bitmap), PublicResources.ArgumentNull); + SetDisplayText(Res.TextCountingColorsId); drawingProgressManager = new DrawingProgressManager(p => { lock (ProgressSyncRoot) @@ -114,6 +117,8 @@ internal void CancelIfRunning() protected override bool AffectsModifiedState(string propertyName) => false; + protected override void ApplyDisplayLanguage() => UpdateDisplayText(); + #endregion #region Private Methods @@ -166,15 +171,27 @@ private void DoCountColors(object? state) // the execution of this method will be marshaled back to the UI thread void Action() { - DisplayText = error != null ? Res.ErrorMessage(error.Message) - : colorCount == null ? Res.TextOperationCanceled - : Res.TextColorCount(colorCount.Value); + if (error != null) + SetDisplayText(Res.ErrorMessageId, error.Message); + else if (colorCount == null) + SetDisplayText(Res.TextOperationCanceledId); + else + SetDisplayText(Res.TextColorCountId, colorCount.Value); IsProcessing = false; } TryInvokeSync(Action); } + private void SetDisplayText(string resourceId, params object[] args) + { + displayTextId = resourceId; + displayTextArgs = args; + UpdateDisplayText(); + } + + private void UpdateDisplayText() => DisplayText = Res.Get(displayTextId, displayTextArgs); + #endregion #region Command Handlers diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index 828f2af..4fae338 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -81,7 +81,7 @@ internal EditResourcesViewModel(CultureInfo culture) this.culture = culture ?? throw new ArgumentNullException(nameof(culture), PublicResources.ArgumentNull); resources = new Dictionary, bool)>(3, EnumComparer.Comparer); ResourceFiles = Enum.GetValues().Select(o => new KeyValuePair(o, ToFileName(o))).ToArray(); - ResetTitle(); + UpdateTitle(); SelectedLibrary = ResourceOwner.DrawingTools; } @@ -104,6 +104,8 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) } } + protected override void ApplyDisplayLanguage() => UpdateTitle(); + protected override void Dispose(bool disposing) { if (IsDisposed) @@ -116,7 +118,7 @@ protected override void Dispose(bool disposing) #region Private Methods - private void ResetTitle() => TitleCaption = Res.TitleEditResources($"{culture.EnglishName} ({culture.NativeName})"); + private void UpdateTitle() => TitleCaption = Res.TitleEditResources($"{culture.EnglishName} ({culture.NativeName})"); private string ToFileName(ResourceOwner owner) => $"{ResHelper.GetBaseName(owner)}.{culture.Name}.resx"; diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs index 0b791bb..d6c16b7 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs @@ -64,6 +64,7 @@ internal class ImageVisualizerViewModel : ViewModelBase, IViewModel, private bool isOpenFilterUpToDate; private Size currentResolution; private bool deferSettingCompoundStateImage; + private string? notificationId; #endregion @@ -96,7 +97,7 @@ internal ImageInfo ImageInfo internal bool ReadOnly { get => Get(); set => Set(value); } internal string? TitleCaption { get => Get(); set => Set(value); } internal string? InfoText { get => Get(); set => Set(value); } - internal string? Notification { get => Get(); set => Set(value); } + internal string? Notification { get => Get(); private set => Set(value); } internal bool AutoZoom { get => Get(); set => Set(value); } internal float Zoom { get => Get(1f); set => Set(value); } internal bool SmoothZooming { get => Get(); set => Set(value); } @@ -304,10 +305,16 @@ protected virtual void OpenFile() if (fileName == null) return; - Notification = null; + SetNotification(null); OpenFile(fileName); } + protected void SetNotification(string? resourceId) + { + notificationId = resourceId; + UpdateNotification(); + } + protected virtual bool OpenFile(string path) { try @@ -384,6 +391,16 @@ protected virtual void Clear() SetModified(IsDebuggerVisualizer); } + protected override void ApplyDisplayLanguage() + { + isOpenFilterUpToDate = false; + UpdateSmoothZoomingTooltip(); + UpdateNotification(); + UpdateInfo(); + if (imageInfo.HasFrames) + UpdateCompoundToolTip(); + } + protected override void Dispose(bool disposing) { if (disposing && !keepAliveImageInfo) @@ -443,18 +460,19 @@ private void InitSingleImage() private void InitMultiImage() { - SetCompoundViewCommandState[stateToolTipText] = imageInfo.Type switch - { - ImageInfoType.Pages => Res.TooltipTextCompoundMultiPage, - ImageInfoType.Animation => Res.TooltipTextCompoundAnimation, - _ => Res.TooltipTextCompoundMultiSize - }; - + UpdateCompoundToolTip(); SetCompoundViewCommandStateImage(); SetCompoundViewCommandState[stateVisible] = true; ResetCompoundState(); } + private void UpdateCompoundToolTip() => SetCompoundViewCommandState[stateToolTipText] = imageInfo.Type switch + { + ImageInfoType.Pages => Res.TooltipTextCompoundMultiPage, + ImageInfoType.Animation => Res.TooltipTextCompoundAnimation, + _ => Res.TooltipTextCompoundMultiSize + }; + private ImageInfoBase GetCurrentImage() { if (!imageInfo.HasFrames || currentFrame < 0 || IsAutoPlaying) @@ -594,7 +612,7 @@ private void FromFile(string fileName) if (image.RawFormat.Guid == ImageFormat.MemoryBmp.Guid) { - Notification = Res.NotificationMetafileAsBitmap; + SetNotification(Res.NotificationMetafileAsBitmapId); openedFileName = null; } } @@ -622,10 +640,9 @@ private void FromFile(string fileName) Bitmap iconImage = image as Bitmap ?? new Bitmap(image); icon = iconImage.ToIcon(); iconImage.Dispose(); - Notification = Res.NotificationImageAsIcon; + SetNotification(Res.NotificationImageAsIconId); openedFileName = null; } - } if (icon != null) @@ -884,17 +901,14 @@ static string GetFirstExtension(string extensions) private void InitAutoZoom(bool viewLoading) { + UpdateSmoothZoomingTooltip(); if (imageInfo.Type == ImageInfoType.None) { SetAutoZoomCommandState.Enabled = AutoZoom = false; - SetSmoothZoomingCommandState[stateToolTipText] = null; return; } SetAutoZoomCommandState.Enabled = SetSmoothZoomingCommandState.Enabled = true; - SetSmoothZoomingCommandState[stateToolTipText] = imageInfo.IsMetafile - ? Res.TooltipTextSmoothMetafile - : Res.TooltipTextSmoothBitmap; // metafile: we always turn on auto zoom and preserve current smooth zooming if (imageInfo.IsMetafile) @@ -1012,6 +1026,14 @@ private void RotateBitmap(RotateFlipType direction) SetCurrentImage((Bitmap)image.Image); } + private void UpdateSmoothZoomingTooltip() + => SetSmoothZoomingCommandState[stateToolTipText] = + imageInfo.Type == ImageInfoType.None ? null + : imageInfo.IsMetafile ? Res.TooltipTextSmoothMetafile + : Res.TooltipTextSmoothBitmap; + + private void UpdateNotification() => Notification = notificationId == null ? null : Res.Get(notificationId); + #endregion #region Explicitly Implemented Interface Methods diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs index cc25714..cd386d1 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs @@ -96,6 +96,15 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) UpdateStatus((string)e.NewValue!); } + protected override void ApplyDisplayLanguage() + { + InitAvailableVersion(); + var currentPath = CurrentPath; + InitInstallations(); + CurrentPath = currentPath; + UpdateStatus(currentPath); + } + #endregion #region Private Methods diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs index 4a49c09..d682307 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs @@ -215,6 +215,8 @@ protected void BeginGeneratePreview() protected abstract bool MatchesOriginal(GenerateTaskBase task); protected virtual void ResetParameters() { } + protected override void ApplyDisplayLanguage() => Validate(); + protected override void Dispose(bool disposing) { if (IsDisposed) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs index 3d1e5dc..256505a 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs @@ -41,6 +41,12 @@ internal abstract class ViewModelBase : ObservableObjectBase, IViewModel #endregion + #region Constructors + + protected ViewModelBase() => LanguageSettings.DisplayLanguageChanged += LanguageSettings_DisplayLanguageChanged; + + #endregion + #region Methods #region Protected Methods @@ -72,6 +78,17 @@ protected bool TryInvokeSync(Action action) return true; } + protected virtual void ApplyDisplayLanguage() { } + + protected override void Dispose(bool disposing) + { + if (IsDisposed) + return; + + LanguageSettings.DisplayLanguageChanged -= LanguageSettings_DisplayLanguageChanged; + base.Dispose(disposing); + } + #endregion #region Internal Methods @@ -80,6 +97,12 @@ protected bool TryInvokeSync(Action action) #endregion + #region Event Handlers + + private void LanguageSettings_DisplayLanguageChanged(object sender, EventArgs e) => ApplyDisplayLanguage(); + + #endregion + #endregion } } \ No newline at end of file From 43ee889f474d3e525ea43fe13c464e4038c0d419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 22 May 2021 14:58:49 +0200 Subject: [PATCH 040/211] Apply command on Edit Resources --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 8 +-- .../View/Forms/EditResourcesForm.Designer.cs | 16 ++--- .../View/Forms/EditResourcesForm.cs | 8 ++- .../View/UserControls/OkCancelApplyButtons.cs | 7 +-- .../UserControls/OkCancelButtons.Designer.cs | 19 +++--- .../ViewModel/EditResourcesViewModel.cs | 61 ++++++++++++++++--- .../ViewModel/LanguageSettingsViewModel.cs | 4 +- 7 files changed, 79 insertions(+), 44 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index be13e60..8c93976 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -629,9 +629,6 @@ Dithering may help to preserve more details. Installable Version: - - - - Identified Visual Studio Versions: @@ -647,9 +644,6 @@ Dithering may help to preserve more details. Status: - - - - &Path: @@ -790,7 +784,7 @@ Newly generated resources will contain English texts prefixed with "[T]". You ca Display Language - Edit Resources... + &Edit Resources... Resource File diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index 0f64cd9..685a809 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -43,7 +43,7 @@ private void InitializeComponent() this.txtOriginalText = new System.Windows.Forms.TextBox(); this.gbTranslatedText = new System.Windows.Forms.GroupBox(); this.txtTranslatedText = new System.Windows.Forms.TextBox(); - this.okCancelButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); + this.okCancelApplyButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelApplyButtons(); this.colResourceKey = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colOriginalText = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colTranslatedText = new System.Windows.Forms.DataGridViewTextBoxColumn(); @@ -193,11 +193,11 @@ private void InitializeComponent() // // okCancelButtons // - this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; - this.okCancelButtons.Location = new System.Drawing.Point(3, 268); - this.okCancelButtons.Name = "okCancelButtons"; - this.okCancelButtons.Size = new System.Drawing.Size(578, 40); - this.okCancelButtons.TabIndex = 1; + this.okCancelApplyButtons.Dock = System.Windows.Forms.DockStyle.Bottom; + this.okCancelApplyButtons.Location = new System.Drawing.Point(3, 268); + this.okCancelApplyButtons.Name = "okCancelApplyButtons"; + this.okCancelApplyButtons.Size = new System.Drawing.Size(578, 40); + this.okCancelApplyButtons.TabIndex = 1; // // colResourceKey // @@ -230,7 +230,7 @@ private void InitializeComponent() this.Controls.Add(this.gbResourceFile); this.Controls.Add(this.splitterEditResources); this.Controls.Add(this.pnlEditResourceEntry); - this.Controls.Add(this.okCancelButtons); + this.Controls.Add(this.okCancelApplyButtons); this.MinimizeBox = false; this.MinimumSize = new System.Drawing.Size(300, 300); this.Name = "EditResourcesForm"; @@ -251,7 +251,7 @@ private void InitializeComponent() #endregion - private UserControls.OkCancelButtons okCancelButtons; + private UserControls.OkCancelApplyButtons okCancelApplyButtons; private System.Windows.Forms.GroupBox gbResourceEntries; private System.Windows.Forms.DataGridView gridResources; private System.Windows.Forms.TableLayoutPanel pnlEditResourceEntry; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs index 6cd7f06..6a3effe 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs @@ -97,14 +97,18 @@ private void InitPropertyBindings() private void InitCommandBindings() { + // ApplyButton.Click -> ViewModel.ApplyResourcesCommand + CommandBindings.Add(ViewModel.ApplyResourcesCommand, ViewModel.ApplyResourcesCommandState) + .AddSource(okCancelApplyButtons.ApplyButton, nameof(okCancelApplyButtons.ApplyButton.Click)); + // OKButton.Click -> ViewModel.SaveResourcesCommand, and preventing closing the form if the command has executed with errors CommandBindings.Add(ViewModel.SaveResourcesCommand) - .AddSource(okCancelButtons.OKButton, nameof(okCancelButtons.OKButton.Click)) + .AddSource(okCancelApplyButtons.OKButton, nameof(okCancelApplyButtons.OKButton.Click)) .Executed += (_, args) => DialogResult = args.State[EditResourcesViewModel.StateSaveExecutedWithError] is true ? DialogResult.None : DialogResult.OK; // CancelButton.Click -> ViewModel.CancelResourcesCommand CommandBindings.Add(ViewModel.CancelEditCommand) - .AddSource(okCancelButtons.CancelButton, nameof(okCancelButtons.CancelButton.Click)); + .AddSource(okCancelApplyButtons.CancelButton, nameof(okCancelApplyButtons.CancelButton.Click)); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelApplyButtons.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelApplyButtons.cs index 97eaf6c..4e6e9f5 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelApplyButtons.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelApplyButtons.cs @@ -35,10 +35,6 @@ internal class OkCancelApplyButtons : OkCancelButtons public OkCancelApplyButtons() { pnlButtons.SuspendLayout(); - pnlButtons.ColumnCount = 3; - foreach (ColumnStyle columnStyle in pnlButtons.ColumnStyles) - columnStyle.Width = 33.3f; - pnlButtons.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 33.3f)); ApplyButton = new Button { Anchor = AnchorStyles.None, @@ -49,7 +45,8 @@ public OkCancelApplyButtons() UseVisualStyleBackColor = true }; - pnlButtons.Controls.Add(ApplyButton, 2, 0); + pnlButtons.Controls.Add(ApplyButton); + ApplyButton.BringToFront(); pnlButtons.ResumeLayout(false); } diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs index 84138f1..6194cd5 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs @@ -15,7 +15,7 @@ partial class OkCancelButtons /// private void InitializeComponent() { - this.pnlButtons = new System.Windows.Forms.TableLayoutPanel(); + this.pnlButtons = new System.Windows.Forms.FlowLayoutPanel(); this.btnOK = new System.Windows.Forms.Button(); this.btnCancel = new System.Windows.Forms.Button(); this.pnlButtons.SuspendLayout(); @@ -23,16 +23,13 @@ private void InitializeComponent() // // pnlButtons // - this.pnlButtons.ColumnCount = 2; - this.pnlButtons.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.pnlButtons.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.pnlButtons.Controls.Add(this.btnOK, 0, 0); - this.pnlButtons.Controls.Add(this.btnCancel, 1, 0); + this.pnlButtons.Controls.Add(this.btnCancel); + this.pnlButtons.Controls.Add(this.btnOK); this.pnlButtons.Dock = System.Windows.Forms.DockStyle.Fill; + this.pnlButtons.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft; this.pnlButtons.Location = new System.Drawing.Point(0, 0); this.pnlButtons.Name = "pnlButtons"; - this.pnlButtons.RowCount = 1; - this.pnlButtons.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.pnlButtons.Padding = new System.Windows.Forms.Padding(3); this.pnlButtons.Size = new System.Drawing.Size(218, 40); this.pnlButtons.TabIndex = 0; // @@ -41,7 +38,7 @@ private void InitializeComponent() this.btnOK.Anchor = System.Windows.Forms.AnchorStyles.None; this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK; this.btnOK.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.btnOK.Location = new System.Drawing.Point(17, 8); + this.btnOK.Location = new System.Drawing.Point(53, 6); this.btnOK.Name = "btnOK"; this.btnOK.Size = new System.Drawing.Size(75, 23); this.btnOK.TabIndex = 0; @@ -53,7 +50,7 @@ private void InitializeComponent() this.btnCancel.Anchor = System.Windows.Forms.AnchorStyles.None; this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; this.btnCancel.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.btnCancel.Location = new System.Drawing.Point(126, 8); + this.btnCancel.Location = new System.Drawing.Point(134, 6); this.btnCancel.Name = "btnCancel"; this.btnCancel.Size = new System.Drawing.Size(75, 23); this.btnCancel.TabIndex = 1; @@ -75,6 +72,6 @@ private void InitializeComponent() #endregion private System.Windows.Forms.Button btnOK; private System.Windows.Forms.Button btnCancel; - protected System.Windows.Forms.TableLayoutPanel pnlButtons; + protected System.Windows.Forms.FlowLayoutPanel pnlButtons; } } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index 4fae338..aa5878c 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -16,6 +16,7 @@ #region Usings +using System.Diagnostics; using System.Linq; using KGySoft.Resources; @@ -69,9 +70,12 @@ internal class EditResourcesViewModel : ViewModelBase internal ResourceOwner SelectedLibrary { get => Get(); set => Set(value); } internal IList SelectedSet { get => Get>(); set => Set(value); } + internal ICommand ApplyResourcesCommand => Get(() => new SimpleCommand(OnApplyResourcesCommand)); internal ICommand SaveResourcesCommand => Get(() => new SimpleCommand(OnSaveResourcesCommand)); internal ICommand CancelEditCommand => Get(() => new SimpleCommand(OnCancelEditCommand)); + internal ICommandState ApplyResourcesCommandState => Get(() => new CommandState()); + #endregion #region Constructors @@ -81,6 +85,7 @@ internal EditResourcesViewModel(CultureInfo culture) this.culture = culture ?? throw new ArgumentNullException(nameof(culture), PublicResources.ArgumentNull); resources = new Dictionary, bool)>(3, EnumComparer.Comparer); ResourceFiles = Enum.GetValues().Select(o => new KeyValuePair(o, ToFileName(o))).ToArray(); + ApplyResourcesCommandState.Enabled = !Equals(LanguageSettings.DisplayLanguage, culture); UpdateTitle(); SelectedLibrary = ResourceOwner.DrawingTools; } @@ -104,7 +109,11 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) } } - protected override void ApplyDisplayLanguage() => UpdateTitle(); + protected override void ApplyDisplayLanguage() + { + UpdateTitle(); + ApplyResourcesCommandState.Enabled = false; + } protected override void Dispose(bool disposing) { @@ -190,6 +199,7 @@ private bool TryReadResources(ResourceOwner owner, [MaybeNullWhen(false)]out ILi if (args.ListChangedType != ListChangedType.ItemChanged) return; + ApplyResourcesCommandState.Enabled = true; if (!resources.TryGetValue(owner, out var value) || value.IsModified) return; @@ -229,11 +239,7 @@ private bool TrySaveResources(ResourceOwner owner, IList set, [Ma } } - #endregion - - #region Command Handlers - - private void OnSaveResourcesCommand(ICommandState state) + private bool TrySaveResources() { foreach (var set in resources) { @@ -243,12 +249,49 @@ private void OnSaveResourcesCommand(ICommandState state) continue; ShowError(Res.ErrorMessageFailedToSaveResource(ToFileName(set.Key), error.Message)); - state[StateSaveExecutedWithError] = true; - return; + return false; } - state[StateSaveExecutedWithError] = false; + return true; + } + + private void ApplyResources() + { + if (Equals(LanguageSettings.DisplayLanguage, culture)) + ResHelper.RaiseLanguageChanged(); + else + LanguageSettings.DisplayLanguage = culture; + SetModified(false); + } + + #endregion + + #region Command Handlers + + private void OnApplyResourcesCommand(ICommandState state) + { + Debug.Assert(IsModified); ResHelper.ReleaseAllResources(); + bool success = TrySaveResources(); + if (success) + ApplyResources(); + } + + private void OnSaveResourcesCommand(ICommandState state) + { + bool success = true; + if (IsModified) + { + ResHelper.ReleaseAllResources(); + success = TrySaveResources(); + if (success) + { + if (Equals(LanguageSettings.DisplayLanguage, culture)) + ApplyResources(); + } + } + + state[StateSaveExecutedWithError] = !success; } private void OnCancelEditCommand() => SetModified(false); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index 016f1a5..7fcd759 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -110,6 +110,8 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) } } + protected override void ApplyDisplayLanguage() => ApplyCommandState.Enabled = false; + protected override void Dispose(bool disposing) { if (IsDisposed) @@ -166,8 +168,6 @@ private void OnApplyCommand() // TODO If used, then add to EditResourcesVM.Save, too, to be consistent () //ResHelper.EnsureResourcesGenerated(); ResHelper.SavePendingResources(); - - ApplyCommandState.Enabled = false; } private void OnSaveConfigCommand() From 0633c92f4c617fc4efee4dc4f8a752166935c007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 22 May 2021 20:20:33 +0200 Subject: [PATCH 041/211] Allowing editing even the English language --- .../ViewModel/EditResourcesViewModel.cs | 7 +- .../ViewModel/LanguageSettingsViewModel.cs | 65 +++++++++++++++---- .../_Classes/ResHelper.cs | 9 +-- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index aa5878c..161130d 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -192,7 +192,9 @@ private bool TryReadResources(ResourceOwner owner, [MaybeNullWhen(false)]out ILi // Note: this way we add even possibly missing entries that were added to compiled resources since last creation while removed entries will be skipped var result = new SortableBindingList(); foreach (DictionaryEntry entry in compiled) - result.Add(new ResourceEntry((string)entry.Key, (string)entry.Value, resourceManger.GetString((string)entry.Key, culture) ?? LanguageSettings.UntranslatedResourcePrefix + entry.Value)); + result.Add(new ResourceEntry((string)entry.Key, + (string)entry.Value, + resourceManger.GetString((string)entry.Key, culture) ?? LanguageSettings.UntranslatedResourcePrefix + (string)entry.Value)); result.ListChanged += (_, args) => { @@ -257,6 +259,7 @@ private bool TrySaveResources() private void ApplyResources() { + LanguageSettings.DynamicResourceManagersSource = ResourceManagerSources.CompiledAndResX; if (Equals(LanguageSettings.DisplayLanguage, culture)) ResHelper.RaiseLanguageChanged(); else @@ -270,7 +273,7 @@ private void ApplyResources() private void OnApplyResourcesCommand(ICommandState state) { - Debug.Assert(IsModified); + Debug.Assert(IsModified || !Equals(culture, LanguageSettings.DisplayLanguage)); ResHelper.ReleaseAllResources(); bool success = TrySaveResources(); if (success) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index 7fcd759..2f32d6b 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Globalization; using KGySoft.ComponentModel; @@ -33,9 +34,10 @@ internal class LanguageSettingsViewModel : ViewModelBase { #region Fields - private readonly bool initializing; - private CultureInfo[]? neutralLanguages; + private HashSet? availableResXLanguages; + private List? selectableLanguages; + private CultureInfo? dirtyCulture; #endregion @@ -61,6 +63,22 @@ internal class LanguageSettingsViewModel : ViewModelBase #region Private Properties private CultureInfo[] NeutralLanguages => neutralLanguages ??= CultureInfo.GetCultures(CultureTypes.NeutralCultures); + private HashSet AvailableLanguages => availableResXLanguages ??= ResHelper.GetAvailableLanguages(); + + private List SelectableLanguages + { + get + { + if (selectableLanguages == null) + { + selectableLanguages = new List(AvailableLanguages); + if (!AvailableLanguages.Contains(Res.DefaultLanguage)) + selectableLanguages.Add(Res.DefaultLanguage); + } + + return selectableLanguages; + } + } #endregion @@ -70,13 +88,12 @@ internal class LanguageSettingsViewModel : ViewModelBase internal LanguageSettingsViewModel() { - initializing = true; CurrentLanguage = LanguageSettings.DisplayLanguage; AllowResXResources = Settings.Default.AllowResXResources; UseOSLanguage = Settings.Default.UseOSLanguage; ExistingLanguagesOnly = true; // could be the default value but this way we spare one reset when initializing binding - initializing = false; ResetLanguages(); + UpdateApplyCommandState(); } #endregion @@ -91,11 +108,15 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) switch (e.PropertyName) { case nameof(AllowResXResources): + EditResourcesCommandState.Enabled = e.NewValue is true; + ResetLanguages(); + UpdateApplyCommandState(); + break; + case nameof(UseOSLanguage): case nameof(ExistingLanguagesOnly): - if (initializing) - return; ResetLanguages(); + UpdateApplyCommandState(); break; case nameof(Languages): @@ -104,13 +125,16 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) break; case nameof(CurrentLanguage): - ApplyCommandState.Enabled = !Equals(e.NewValue, LanguageSettings.DisplayLanguage); - EditResourcesCommandState.Enabled = !Equals(e.NewValue, Res.DefaultLanguage); + UpdateApplyCommandState(); break; } } - protected override void ApplyDisplayLanguage() => ApplyCommandState.Enabled = false; + protected override void ApplyDisplayLanguage() + { + Debug.Assert(Equals(LanguageSettings.DisplayLanguage, CurrentLanguage), "Only the selected language should be applied"); + UpdateApplyCommandState(); + } protected override void Dispose(bool disposing) { @@ -143,19 +167,32 @@ private void ResetLanguages() return; } - var result = new SortableBindingList(ExistingLanguagesOnly ? ResHelper.GetAvailableLanguages() : NeutralLanguages); + var result = new SortableBindingList(ExistingLanguagesOnly ? SelectableLanguages : NeutralLanguages); result.ApplySort(nameof(CultureInfo.EnglishName), ListSortDirection.Ascending); CultureInfo lastSelectedLanguage = CurrentLanguage; Languages = result; CurrentLanguage = result.Contains(lastSelectedLanguage) ? lastSelectedLanguage : Res.DefaultLanguage; } + private void UpdateApplyCommandState() + { + // Apply is enabled if current language is different than display language, + // or when turning on/off .resx resources for the default language matters because it also has a resource file + CultureInfo selected = CurrentLanguage; + ApplyCommandState.Enabled = !Equals(selected, LanguageSettings.DisplayLanguage) + || selected.Equals(dirtyCulture) + || (Equals(selected, Res.DefaultLanguage) + && (AllowResXResources ^ LanguageSettings.DynamicResourceManagersSource != ResourceManagerSources.CompiledOnly) + && AvailableLanguages.Contains(Res.DefaultLanguage)); + } + #endregion #region Command Handlers private void OnApplyCommand() { + dirtyCulture = null; CultureInfo currentLanguage = CurrentLanguage; LanguageSettings.DynamicResourceManagersSource = AllowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; @@ -168,6 +205,8 @@ private void OnApplyCommand() // TODO If used, then add to EditResourcesVM.Save, too, to be consistent () //ResHelper.EnsureResourcesGenerated(); ResHelper.SavePendingResources(); + availableResXLanguages = null; + selectableLanguages = null; } private void OnSaveConfigCommand() @@ -198,8 +237,10 @@ private void OnEditResourcesCommand() ShowChildViewCallback?.Invoke(viewModel); // If the language was edited, then enabling apply even if it was disabled - if (viewModel.IsModified) - ApplyCommandState.Enabled = true; + dirtyCulture = viewModel.IsModified ? CurrentLanguage : null; + availableResXLanguages = null; + selectableLanguages = null; + UpdateApplyCommandState(); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs index f5523fe..d89a2a6 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs @@ -65,12 +65,12 @@ private static DynamicResourceManager[] KnownResourceManagers #region Methods - internal static IList GetAvailableLanguages() + internal static HashSet GetAvailableLanguages() { string dir = Res.ResourcesDir; + var result = new HashSet(); try { - var result = new List { Res.DefaultLanguage }; if (!Directory.Exists(dir)) return result; @@ -79,7 +79,7 @@ internal static IList GetAvailableLanguages() foreach (string file in files) { StringSegment resName = file.AsSegment(startIndex, file.Length - startIndex - 5); - if (CulturesCache.TryGetValue(resName, out CultureInfo? ci) && !ci.In(CultureInfo.InvariantCulture, Res.DefaultLanguage)) + if (CulturesCache.TryGetValue(resName, out CultureInfo? ci) && !ci.Equals(CultureInfo.InvariantCulture)) result.Add(ci); } @@ -87,7 +87,8 @@ internal static IList GetAvailableLanguages() } catch (Exception e) when (!e.IsCritical()) { - return new[] { Res.DefaultLanguage }; + result.Clear(); + return result; } } From d4f203f397592d261a1ab010a3b40af3e606d16c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 23 May 2021 14:06:53 +0200 Subject: [PATCH 042/211] Handling invariant and region-specific languages --- KGySoft.Drawing.ImagingTools/Res.cs | 8 +++++--- .../ViewModel/EditResourcesViewModel.cs | 11 +++++++++-- .../ViewModel/LanguageSettingsViewModel.cs | 2 +- KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs | 3 +++ changelog.txt | 1 + 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index f85c43b..01d7e06 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -252,9 +252,11 @@ static Res() DrawingModule.Initialize(); bool allowResXResources = Settings.Default.AllowResXResources; - LanguageSettings.DisplayLanguage = allowResXResources - ? Settings.Default.UseOSLanguage ? OSLanguage : Settings.Default.DisplayLanguage.GetClosestNeutralCulture() + CultureInfo desiredDisplayLanguage = allowResXResources + ? Settings.Default.UseOSLanguage ? OSLanguage : Settings.Default.DisplayLanguage // here, allowing specific languages, too : DefaultLanguage; + + LanguageSettings.DisplayLanguage = Equals(desiredDisplayLanguage, CultureInfo.InvariantCulture) ? DefaultLanguage : desiredDisplayLanguage; LanguageSettings.DynamicResourceManagersSource = allowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; } @@ -608,7 +610,7 @@ private static string SafeFormat(string format, object?[] args) private static CultureInfo GetClosestNeutralCulture(this CultureInfo culture) { if (CultureInfo.InvariantCulture.Equals(culture)) - return culture; + return CultureInfo.GetCultureInfo("en"); while (!culture.IsNeutralCulture) culture = culture.Parent; diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index 161130d..3e70448 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -59,6 +59,7 @@ internal class EditResourcesViewModel : ViewModelBase #region Fields private readonly CultureInfo culture; + private readonly bool useInvariant; private readonly Dictionary ResourceSet, bool IsModified)> resources; #endregion @@ -83,8 +84,12 @@ internal class EditResourcesViewModel : ViewModelBase internal EditResourcesViewModel(CultureInfo culture) { this.culture = culture ?? throw new ArgumentNullException(nameof(culture), PublicResources.ArgumentNull); + + // The default language is used as the invariant resource set. + // The invariant file name is preferred, unless only the language-specific file exists. + useInvariant = Equals(culture, Res.DefaultLanguage) && !File.Exists(ToFileNameWithPath(ResourceOwner.DrawingTools)); resources = new Dictionary, bool)>(3, EnumComparer.Comparer); - ResourceFiles = Enum.GetValues().Select(o => new KeyValuePair(o, ToFileName(o))).ToArray(); + ResourceFiles = Enum.GetValues().Select(owner => new KeyValuePair(owner, ToFileName(owner))).ToArray(); ApplyResourcesCommandState.Enabled = !Equals(LanguageSettings.DisplayLanguage, culture); UpdateTitle(); SelectedLibrary = ResourceOwner.DrawingTools; @@ -129,7 +134,9 @@ protected override void Dispose(bool disposing) private void UpdateTitle() => TitleCaption = Res.TitleEditResources($"{culture.EnglishName} ({culture.NativeName})"); - private string ToFileName(ResourceOwner owner) => $"{ResHelper.GetBaseName(owner)}.{culture.Name}.resx"; + private string ToFileName(ResourceOwner owner) => useInvariant + ? ResHelper.GetBaseName(owner) + ".resx" + : $"{ResHelper.GetBaseName(owner)}.{culture.Name}.resx"; private string ToFileNameWithPath(ResourceOwner owner) => Path.Combine(Res.ResourcesDir, ToFileName(owner)); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index 2f32d6b..0b79c97 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -47,7 +47,7 @@ internal class LanguageSettingsViewModel : ViewModelBase internal bool AllowResXResources { get => Get(); set => Set(value); } internal bool UseOSLanguage { get => Get(); set => Set(value); } - internal bool ExistingLanguagesOnly { get => Get(); set => Set(value); } + internal bool ExistingLanguagesOnly { get => Get(true); set => Set(value); } internal CultureInfo CurrentLanguage { get => Get(); set => Set(value); } internal IList Languages { get => Get>(); set => Set(value); } diff --git a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs index d89a2a6..0580021 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs @@ -83,6 +83,9 @@ internal static HashSet GetAvailableLanguages() result.Add(ci); } + // checking the invariant resource as it should act as default language + if (!result.Contains(Res.DefaultLanguage) && File.Exists(Path.Combine(dir, $"{drawingToolsBaseName}.resx"))) + result.Add(Res.DefaultLanguage); return result; } catch (Exception e) when (!e.IsCritical()) diff --git a/changelog.txt b/changelog.txt index e756f0d..0719392 100644 --- a/changelog.txt +++ b/changelog.txt @@ -15,6 +15,7 @@ + Now panning a zoomed image is possible also by clicking and dragging with the mouse (besides usual scrolling). + Zooming is now possible also with keyboard shortcuts, the Auto Zoom button has now a drop-down part for the additional options. ++ Supporting localization from .resx files with editing and on-the-fly application. * Manual zooming is enabled even if Auto Zoom is on; this turns off Auto Zoom automatically. * Turning smoothing of zoomed images on/off affects also shrunk images (previously affected enlarged images only). - Fixing possible errors when closing forms while an async operation is still in progress. From da6a1449eb2ec201ec53dfecb8a9a98b01ae6994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 23 May 2021 19:57:33 +0200 Subject: [PATCH 043/211] ToolTip timeout --- .../View/Forms/MvvmBaseForm.Designer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs index 07c43c4..1b0bf17 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs @@ -11,6 +11,10 @@ private void InitializeComponent() this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.SuspendLayout(); // + // toolTip + // + this.toolTip.AutoPopDelay = 10000; + // // MvvmBaseForm // this.ClientSize = new System.Drawing.Size(284, 261); From 8c32af1fa52a4ac0b3d370827d452503d99e8b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 23 May 2021 22:18:11 +0200 Subject: [PATCH 044/211] Applying a Right-To-Left layout when switching to an RTL language --- .../View/Forms/MvvmBaseForm.cs | 78 +++++++++++++++++-- .../View/ViewFactory.cs | 17 +++- .../View/_Interfaces/IView.cs | 20 ++++- changelog.txt | 4 + 4 files changed, 107 insertions(+), 12 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index 9d71052..73cea5d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -17,6 +17,7 @@ #region Usings using System; +using System.Drawing; using System.Windows.Forms; using KGySoft.ComponentModel; @@ -32,6 +33,9 @@ internal partial class MvvmBaseForm : BaseForm, IView #region Fields private bool isClosing; + private bool isLoaded; + private bool isRtlChanging; + private Point location; #endregion @@ -59,6 +63,7 @@ internal partial class MvvmBaseForm : BaseForm, IView protected MvvmBaseForm(TViewModel viewModel) { + ApplyRightToLeft(); InitializeComponent(); // occurs in design mode but DesignMode is false for grandchild forms @@ -66,7 +71,6 @@ protected MvvmBaseForm(TViewModel viewModel) return; ViewModel = viewModel; - ViewModelBase vm = VM; vm.ShowInfoCallback = Dialogs.InfoMessage; vm.ShowWarningCallback = Dialogs.WarningMessage; @@ -98,10 +102,20 @@ protected override void OnLoad(EventArgs e) { base.OnLoad(e); - // occurs in design mode but DesignMode is false for grandchild forms - if (ViewModel == null!) + // Null VM occurs in design mode but DesignMode is false for grandchild forms + // Loaded can be true if handle was recreated + if (isLoaded || ViewModel == null!) + { + if (!isRtlChanging) + return; + + // dialog has been reopened after changing RTL + isRtlChanging = false; + Location = location; return; - + } + + isLoaded = true; ApplyResources(); ApplyViewModel(); } @@ -118,6 +132,16 @@ protected virtual void ApplyViewModel() protected override void OnFormClosing(FormClosingEventArgs e) { + // Changing RightToLeft causes the dialog close. We let it happen because the parent may also change, + // and if we cancel the closing here, then a dialog may turn a non-modal form. Reopen as a dialog is handled in IView.ShowDialog + if (isRtlChanging) + { + if (DialogResult == DialogResult.OK) + isRtlChanging = false; + else + location = Location; + } + if (!e.Cancel) isClosing = true; base.OnFormClosing(e); @@ -140,11 +164,11 @@ protected override void Dispose(bool disposing) private void InitCommandBindings() { - CommandBindings.Add(ApplyStringResources) + CommandBindings.Add(OnDisplayLanguageChangedCommand) .AddSource(typeof(LanguageSettings), nameof(LanguageSettings.DisplayLanguageChanged)); } - private void ShowChildView(IViewModel vm) => ViewFactory.ShowDialog(vm, Handle); + private void ShowChildView(IViewModel vm) => ViewFactory.ShowDialog(vm, this); private void InvokeIfRequired(Action action) { @@ -163,11 +187,51 @@ private void InvokeIfRequired(Action action) } } + private void ApplyRightToLeft() + { + RightToLeft rtl = LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft ? RightToLeft.Yes : RightToLeft.No; + if (RightToLeft == rtl) + return; + + if (!IsHandleCreated) + { + RightToLeft = rtl; + return; + } + + isRtlChanging = true; + RightToLeft = rtl; + } + + #endregion + + #region Command Handlers + + private void OnDisplayLanguageChangedCommand() + { + ApplyRightToLeft(); + ApplyStringResources(); + } + #endregion #region Explicit Interface Implementations - void IView.ShowDialog(IntPtr ownerHandle) => ShowDialog(ownerHandle == IntPtr.Zero ? null : new OwnerWindowHandle(ownerHandle)); + void IView.ShowDialog(IntPtr ownerHandle) + { + do + { + ShowDialog(ownerHandle == IntPtr.Zero ? null : new OwnerWindowHandle(ownerHandle)); + } while (isRtlChanging); + } + + void IView.ShowDialog(IView? owner) + { + do + { + ShowDialog(owner is IWin32Window window ? window : null); + } while (isRtlChanging); + } void IView.Show() => InvokeIfRequired(() => { diff --git a/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs b/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs index 24f3a4f..d17fb86 100644 --- a/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs +++ b/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs @@ -84,8 +84,21 @@ public static IView CreateView(IViewModel viewModel) /// A view for the specified instance. public static void ShowDialog(IViewModel viewModel, IntPtr ownerWindowHandle = default) { - using (IView view = CreateView(viewModel)) - view.ShowDialog(ownerWindowHandle); + using IView view = CreateView(viewModel); + view.ShowDialog(ownerWindowHandle); + } + + /// + /// Shows an internally created view for the specified instance, + /// which will be discarded when the view is closed. + /// + /// The view model to create the view for. + /// If not , then the created dialog will be owned by the specified instance. + /// A view for the specified instance. + public static void ShowDialog(IViewModel viewModel, IView? owner) + { + using IView view = CreateView(viewModel); + view.ShowDialog(owner); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/_Interfaces/IView.cs b/KGySoft.Drawing.ImagingTools/View/_Interfaces/IView.cs index 712fc0c..def24a9 100644 --- a/KGySoft.Drawing.ImagingTools/View/_Interfaces/IView.cs +++ b/KGySoft.Drawing.ImagingTools/View/_Interfaces/IView.cs @@ -17,7 +17,6 @@ #region Usings using System; -using System.Windows.Forms; #endregion @@ -29,22 +28,37 @@ namespace KGySoft.Drawing.ImagingTools.View /// public interface IView : IDisposable { + #region Properties + + /// + /// Gets whether this view is disposed. + /// + public bool IsDisposed { get; } + + #endregion + + #region Methods + /// /// Shows the view as a modal dialog. + /// When using this overload, do not let the handle of the owner destroyed (some operations such as changing right-to-left may cause the handle recreated). /// /// If specified, then the created dialog will be owned by the window that has specified handle. This parameter is optional. ///
Default value: IntPtr.Zero. void ShowDialog(IntPtr ownerWindowHandle = default); /// - /// Gets whether this view is disposed. + /// Shows the view as a modal dialog. /// - bool IsDisposed { get; } + /// If not , then the created dialog will be owned by the specified instance. + void ShowDialog(IView? owner); /// /// Shows the view as a non-modal window. /// If the view was already shown, then makes it the active window. /// void Show(); + + #endregion } } \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index 0719392..a43be76 100644 --- a/changelog.txt +++ b/changelog.txt @@ -28,6 +28,10 @@ + New IsModified property * IViewModel interface * The IsModified property is now inherited from IViewModel + + IView interface: + + New ShowDialog(IView) overload + + ViewFactory class: + + New ShowDialog(IViewModel, IView) overload - KGySoft.Drawing.DebuggerVisualizers.dll ========================================= From 8563d1f79cc55b903a3352f059bd4bdba30baec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 24 May 2021 20:04:34 +0200 Subject: [PATCH 045/211] Right to left improvements --- .../View/Controls/CheckGroupBox.cs | 34 +++++- .../DrawingProgressStatusStrip.Designer.cs | 1 + .../View/Controls/ImageViewer.cs | 109 ++++++++++-------- .../View/Controls/PalettePanel.cs | 79 +++++++++---- .../View/Controls/ScalingToolStrip.cs | 36 ++++++ .../Forms/AdjustColorsFormBase.Designer.cs | 1 + .../View/Forms/MvvmBaseForm.Designer.cs | 4 + .../View/Forms/MvvmBaseForm.cs | 8 +- .../ColorVisualizerControl.Designer.cs | 10 +- .../DithererStrengthEditorControl.Designer.cs | 1 + .../DithererStrengthEditorControl.cs | 1 + ...uantizerThresholdEditorControl.Designer.cs | 1 + .../QuantizerThresholdEditorControl.cs | 1 + 13 files changed, 199 insertions(+), 87 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs index c81e231..b4aacb5 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs @@ -82,9 +82,7 @@ public CheckGroupBox() { InitializeComponent(); Controls.Add(checkBox); - - // Left should be 10 at 100% but only 8 at 175%, etc. - checkBox.Left = Math.Max(1, 13 - (int)(this.GetScale().X * Padding.Left)); + checkBox.SizeChanged += CheckBox_SizeChanged; // Vista or later: using System FlayStyle so animation is enabled with theming and text is not misplaced with classic themes bool visualStylesEnabled = Application.RenderWithVisualStyles; @@ -123,11 +121,25 @@ protected override void OnControlAdded(ControlEventArgs e) return; // when not in design mode, adding custom controls to a panel so we can toggle its Enabled with preserving their original state - if (contentPanel.Parent == null) - contentPanel.Parent = this; + contentPanel.Parent ??= this; e.Control.Parent = contentPanel; } + protected virtual void OnCheckedChanged(EventArgs e) => (Events[nameof(CheckedChanged)] as EventHandler)?.Invoke(this, e); + + protected override void OnLayout(LayoutEventArgs levent) + { + base.OnLayout(levent); + if (levent.AffectedProperty == nameof(Bounds)) + ResetCheckBoxLocation(); + } + + protected override void OnRightToLeftChanged(EventArgs e) + { + base.OnRightToLeftChanged(e); + ResetCheckBoxLocation(); + } + protected override void Dispose(bool disposing) { if (disposing) @@ -137,6 +149,7 @@ protected override void Dispose(bool disposing) } checkBox.CheckedChanged -= CheckBox_CheckedChanged; + checkBox.SizeChanged -= CheckBox_SizeChanged; base.Dispose(disposing); } @@ -144,7 +157,10 @@ protected override void Dispose(bool disposing) #region Private Methods - private void OnCheckedChanged(EventArgs e) => (Events[nameof(CheckedChanged)] as EventHandler)?.Invoke(this, e); + private void ResetCheckBoxLocation() + => checkBox.Left = RightToLeft == RightToLeft.No + ? (int)(10 * this.GetScale().X) + : Width - checkBox.Width - (int)(10 * this.GetScale().X); #endregion @@ -157,6 +173,12 @@ private void CheckBox_CheckedChanged(object? sender, EventArgs e) OnCheckedChanged(EventArgs.Empty); } + private void CheckBox_SizeChanged(object sender, EventArgs e) + { + if (RightToLeft == RightToLeft.Yes) + ResetCheckBoxLocation(); + } + #endregion #region Explicitly Implemented Interface Methods diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.Designer.cs index b86e2b3..c823f54 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.Designer.cs @@ -25,6 +25,7 @@ private void InitializeComponent() // this.pbProgress.AutoSize = false; this.pbProgress.Name = "pbProgress"; + this.pbProgress.RightToLeftLayout = true; this.pbProgress.Size = new System.Drawing.Size(100, 16); // // timer diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 3e7a491..85e91f2 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -660,6 +660,45 @@ public ImageViewer() #region Methods + #region Internal Methods + + /// + /// Should be called when image content is changed while image reference remains the same (eg. rotation, palette change) + /// + internal void UpdateImage() + { + if (image == null) + return; + + // can happen when image is rotated + if (image.Size != imageSize || !ReferenceEquals(image, previewGenerator.GetDisplayImage(false))) + SetImage(image); + else + Invalidate(); + } + + internal void IncreaseZoom() + { + SetAutoZoom(false, false); + ApplyZoomChange(0.25f); + } + + internal void DecreaseZoom() + { + SetAutoZoom(false, false); + ApplyZoomChange(-0.25f); + } + + internal void ResetZoom() + { + if (zoom.Equals(1f)) + return; + AutoZoom = false; + Zoom = 1f; + } + + #endregion + #region Protected Methods protected override void OnSizeChanged(EventArgs e) @@ -763,6 +802,8 @@ protected override void OnMouseHWheel(HandledMouseEventArgs e) HorizontalScroll(-e.Delta); } + protected override void OnRightToLeftChanged(EventArgs e) => AdjustSizes(); + protected override void Dispose(bool disposing) { if (IsDisposed) @@ -781,45 +822,6 @@ protected override void Dispose(bool disposing) #endregion - #region Internal Methods - - /// - /// Should be called when image content is changed while image reference remains the same (eg. rotation, palette change) - /// - internal void UpdateImage() - { - if (image == null) - return; - - // can happen when image is rotated - if (image.Size != imageSize || !ReferenceEquals(image, previewGenerator.GetDisplayImage(false))) - SetImage(image); - else - Invalidate(); - } - - internal void IncreaseZoom() - { - SetAutoZoom(false, false); - ApplyZoomChange(0.25f); - } - - internal void DecreaseZoom() - { - SetAutoZoom(false, false); - ApplyZoomChange(-0.25f); - } - - internal void ResetZoom() - { - if (zoom.Equals(1f)) - return; - AutoZoom = false; - Zoom = 1f; - } - - #endregion - #region Private Methods private void SetImage(Image? value) @@ -916,11 +918,11 @@ private void AdjustSizes() return; } + Point clientLocation = Point.Empty; targetLocation = new Point((clientSize.Width >> 1) - (scaledSize.Width >> 1), (clientSize.Height >> 1) - (scaledSize.Height >> 1)); - targetRectangle = new Rectangle(targetLocation, scaledSize); - clientRectangle = new Rectangle(Point.Empty, clientSize); + bool isRtl = RightToLeft == RightToLeft.Yes; // both scrollbars if (sbHorizontalVisible && sbVerticalVisible) @@ -928,8 +930,9 @@ private void AdjustSizes() sbHorizontal.Dock = sbVertical.Dock = DockStyle.None; sbHorizontal.Width = clientSize.Width; sbHorizontal.Top = clientSize.Height; + sbHorizontal.Left = isRtl ? scrollbarSize.Width : 0; sbVertical.Height = clientSize.Height; - sbVertical.Left = clientSize.Width; + sbVertical.Left = isRtl ? 0 : clientSize.Width; } // horizontal scrollbar else if (sbHorizontalVisible) @@ -939,14 +942,14 @@ private void AdjustSizes() // vertical scrollbar else if (sbVerticalVisible) { - sbVertical.Dock = DockStyle.Right; + sbVertical.Dock = isRtl ? DockStyle.Left : DockStyle.Right; } // adjust scrollbar values if (sbHorizontalVisible) { - sbHorizontal.Minimum = targetRectangle.X; - sbHorizontal.Maximum = targetRectangle.Right; + sbHorizontal.Minimum = targetLocation.X; + sbHorizontal.Maximum = targetLocation.X + scaledSize.Width; sbHorizontal.LargeChange = clientSize.Width; sbHorizontal.SmallChange = this.ScaleSize(referenceScrollSize).Width; sbHorizontal.Value = Math.Min(sbHorizontal.Value, sbHorizontal.Maximum - sbHorizontal.LargeChange); @@ -954,8 +957,14 @@ private void AdjustSizes() if (sbVerticalVisible) { - sbVertical.Minimum = targetRectangle.Y; - sbVertical.Maximum = targetRectangle.Bottom; + if (isRtl) + { + targetLocation.X += scrollbarSize.Width; + clientLocation.X = scrollbarSize.Width; + } + + sbVertical.Minimum = targetLocation.Y; + sbVertical.Maximum = targetLocation.Y + scaledSize.Height; sbVertical.LargeChange = clientSize.Height; sbVertical.SmallChange = this.ScaleSize(referenceScrollSize).Height; sbVertical.Value = Math.Min(sbVertical.Value, sbVertical.Maximum - sbVertical.LargeChange); @@ -966,8 +975,12 @@ private void AdjustSizes() Cursor = sbHorizontalVisible || sbVerticalVisible ? Cursors.HandOpen : null; isDragging = false; - clientRectangle = new Rectangle(Point.Empty, clientSize); + clientRectangle = new Rectangle(clientLocation, clientSize); targetRectangle = new Rectangle(targetLocation, scaledSize); + if (!isRtl || !sbVerticalVisible) + return; + + clientRectangle.X = scrollbarSize.Width; } private void PaintImage(Graphics g) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs index 84938ce..2a6826d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs @@ -52,6 +52,8 @@ internal sealed partial class PalettePanel : BaseControl private int counter; private PointF scale = new PointF(1f, 1f); private int scrollFraction; + private int scrollbarWidth; + private bool isRightToLeft; #endregion @@ -80,7 +82,7 @@ internal IList? Palette timerSelection.Enabled = false; if (value != null) SelectedColorIndex = 0; - Invalidate(); + ResetLayout(); } } @@ -111,6 +113,7 @@ internal int SelectedColorIndex timerSelection.Enabled = true; if (invalidateAll) { + ResetLayout(); sbPalette.Value = firstVisibleColor >> 4; Invalidate(); } @@ -164,7 +167,8 @@ internal PalettePanel() DoubleBuffered = true; SetStyle(ControlStyles.Selectable, true); - sbPalette.Width = SystemInformation.VerticalScrollBarWidth; + scrollbarWidth = SystemInformation.VerticalScrollBarWidth; + sbPalette.Width = scrollbarWidth; } #endregion @@ -186,9 +190,6 @@ protected override void Dispose(bool disposing) protected override void OnPaint(PaintEventArgs e) { scale = e.Graphics.GetScale(); - if (CheckPaletteLayout()) - return; - base.OnPaint(e); if (ColorCount == 0) return; @@ -264,14 +265,23 @@ protected override void OnMouseDown(MouseEventArgs e) if (e.Button != MouseButtons.Left || ColorCount == 0) return; + Point location = e.Location; + if (isRightToLeft) + { + location.X -= scrollbarWidth; + location.X = (int)MathF.Round(((13 << 4) + 2) * scale.X) - location.X; + } + + // same as before (using the raw location because GetColorRect translates it) if (GetColorRect(selectedColorIndex).Contains(e.Location)) return; - if (!Rectangle.Round(new RectangleF(2 * scale.X, 2 * scale.Y, (13 << 4) * scale.X, 13 * visibleRowCount * scale.Y)).Contains(e.Location)) + // out of range + if (!Rectangle.Round(new RectangleF(2 * scale.X, 2 * scale.Y, (13 << 4) * scale.X, 13 * visibleRowCount * scale.Y)).Contains(location)) return; - int x = ((int)(e.X / scale.X) - 2) / 13; - int y = ((int)(e.Y / scale.Y) - 2) / 13; + int x = ((int)(location.X / scale.X) - 2) / 13; + int y = ((int)(location.Y / scale.Y) - 2) / 13; int index = firstVisibleColor + (y << 4) + x; if (index >= ColorCount) @@ -292,10 +302,11 @@ protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e) switch (e.KeyData) { case Keys.Right: - newIndex = Math.Min(selectedColorIndex + 1, ColorCount - 1); - break; case Keys.Left: - newIndex = Math.Max(selectedColorIndex - 1, 0); + if (e.KeyData == Keys.Right ^ isRightToLeft) + newIndex = Math.Min(selectedColorIndex + 1, ColorCount - 1); + else + newIndex = Math.Max(selectedColorIndex - 1, 0); break; case Keys.Down: newIndex = Math.Min(selectedColorIndex + 16, ColorCount - 1); @@ -343,28 +354,40 @@ protected override void OnMouseWheel(MouseEventArgs e) sbPalette.Value = newValue; } + protected override void OnSizeChanged(EventArgs e) + { + base.OnSizeChanged(e); + ResetLayout(); + } + + protected override void OnRightToLeftChanged(EventArgs e) + { + base.OnRightToLeftChanged(e); + isRightToLeft = RightToLeft == RightToLeft.Yes; + sbPalette.Dock = isRightToLeft ? DockStyle.Left : DockStyle.Right; + Invalidate(); + } + #endregion #region Private Methods - /// - /// Checks the scrollbar and returns on layout change (which means invalidated graphics) - /// - private bool CheckPaletteLayout() + private void ResetLayout() { if (ColorCount == 0) { sbPalette.Visible = false; visibleRowCount = 0; - return false; + return; } + Invalidate(); + // calculating visible rows int maxRows = ((int)(Height / scale.Y) - 5) / 13; if (maxRows == visibleRowCount) - return false; + return; - Invalidate(); visibleRowCount = maxRows; int colorRows = (int)Math.Ceiling((double)palette!.Count / 16); if (visibleRowCount >= colorRows) @@ -373,7 +396,8 @@ private bool CheckPaletteLayout() firstVisibleColor = 0; sbPalette.Visible = false; timerSelection.Enabled = true; - return true; + visibleRowCount = colorRows; + return; } // scrollbar is needed @@ -382,18 +406,25 @@ private bool CheckPaletteLayout() if (firstVisibleColor + (visibleRowCount << 4) >= palette.Count + 16) firstVisibleColor = palette.Count - (visibleRowCount << 4); sbPalette.Value = firstVisibleColor >> 4; + sbPalette.Visible = true; timerSelection.Enabled = IsSelectedColorVisible(); - return true; } private Rectangle GetColorRect(int index) { - float left = (2 + (index % 16) * 13) * scale.X; + float left = index % 16; + if (isRightToLeft) + left = 15 - left; + left = (2 + left * 13) * scale.X; + if (isRightToLeft) + left += scrollbarWidth; + + //float left = (2 + (index % 16) * 13) * scale.X; float top = (2 + ((index - firstVisibleColor) >> 4) * 13) * scale.Y; - // ReSharper disable CompareOfFloatsByEqualityOperator - intended + + // ReSharper disable once CompareOfFloatsByEqualityOperator - intended return new Rectangle(left % 1 == 0 ? (int)left : (int)left + 1, top % 1 == 0 ? (int)top : (int)top + 1, (int)(13 * scale.X), (int)(13 * scale.Y)); - // ReSharper restore CompareOfFloatsByEqualityOperator } private bool IsSelectedColorVisible() @@ -412,7 +443,7 @@ private void sbPalette_ValueChanged(object? sender, EventArgs e) { firstVisibleColor = sbPalette.Value << 4; timerSelection.Enabled = IsSelectedColorVisible(); - Invalidate(); + ResetLayout(); } private void timerSelection_Tick(object? sender, EventArgs e) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs index 6a45638..a7c970b 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs @@ -181,10 +181,21 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr #region Fields + #region Static Fields + private static readonly Size referenceSize = new Size(16, 16); #endregion + #region Instance Fields + + private DockStyle explicitDock = DockStyle.Top; + private bool isAdjustingRtl; + + #endregion + + #endregion + #region Constructors public ScalingToolStrip() @@ -214,6 +225,31 @@ protected override void OnItemAdded(ToolStripItemEventArgs e) base.OnItemAdded(e); } + protected override void OnDockChanged(EventArgs e) + { + base.OnDockChanged(e); + if (isAdjustingRtl) + return; + explicitDock = Dock; + } + + protected override void OnRightToLeftChanged(EventArgs e) + { + base.OnRightToLeftChanged(e); + DockStyle dock = Dock; + if (dock is not (DockStyle.Left or DockStyle.Right)) + return; + + bool isMirrored = RightToLeft == RightToLeft.Yes; + if (isMirrored ^ dock == explicitDock) + return; + isAdjustingRtl = true; + Dock = isMirrored + ? explicitDock == DockStyle.Left ? DockStyle.Right : DockStyle.Left + : explicitDock; + isAdjustingRtl = false; + } + #endregion } } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.Designer.cs index 11d1dbe..137ddc4 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.Designer.cs @@ -39,6 +39,7 @@ private void InitializeComponent() this.trackBar.Dock = System.Windows.Forms.DockStyle.Fill; this.trackBar.Location = new System.Drawing.Point(35, 25); this.trackBar.Name = "trackBar"; + this.trackBar.RightToLeftLayout = true; this.trackBar.Size = new System.Drawing.Size(161, 31); this.trackBar.TabIndex = 2; // diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs index 1b0bf17..e4b1779 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs @@ -14,12 +14,16 @@ private void InitializeComponent() // toolTip // this.toolTip.AutoPopDelay = 10000; + this.toolTip.InitialDelay = 500; + this.toolTip.ReshowDelay = 100; // // MvvmBaseForm // this.ClientSize = new System.Drawing.Size(284, 261); this.Name = "MvvmBaseForm"; + this.RightToLeftLayout = true; this.ResumeLayout(false); + } } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index 73cea5d..29669b8 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -193,13 +193,9 @@ private void ApplyRightToLeft() if (RightToLeft == rtl) return; - if (!IsHandleCreated) - { - RightToLeft = rtl; - return; - } + if (IsHandleCreated) + isRtlChanging = true; - isRtlChanging = true; RightToLeft = rtl; } diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs index 352701a..874abae 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs @@ -32,7 +32,7 @@ private void InitializeComponent() this.lblAlpha = new System.Windows.Forms.Label(); this.pnlAlpha = new System.Windows.Forms.Panel(); this.tbAlpha = new System.Windows.Forms.TrackBar(); - this.tsMenu = new ScalingToolStrip(); + this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStrip(); this.btnSelectColor = new System.Windows.Forms.ToolStripButton(); this.txtColor = new System.Windows.Forms.TextBox(); this.colorDialog = new System.Windows.Forms.ColorDialog(); @@ -114,6 +114,7 @@ private void InitializeComponent() this.tbRed.Location = new System.Drawing.Point(0, 0); this.tbRed.Maximum = 255; this.tbRed.Name = "tbRed"; + this.tbRed.RightToLeftLayout = true; this.tbRed.Size = new System.Drawing.Size(78, 18); this.tbRed.TabIndex = 0; this.tbRed.TickFrequency = 64; @@ -146,6 +147,7 @@ private void InitializeComponent() this.tbGreen.Location = new System.Drawing.Point(0, 0); this.tbGreen.Maximum = 255; this.tbGreen.Name = "tbGreen"; + this.tbGreen.RightToLeftLayout = true; this.tbGreen.Size = new System.Drawing.Size(78, 18); this.tbGreen.TabIndex = 0; this.tbGreen.TickFrequency = 64; @@ -178,6 +180,7 @@ private void InitializeComponent() this.tbBlue.Location = new System.Drawing.Point(0, 0); this.tbBlue.Maximum = 255; this.tbBlue.Name = "tbBlue"; + this.tbBlue.RightToLeftLayout = true; this.tbBlue.Size = new System.Drawing.Size(78, 18); this.tbBlue.TabIndex = 0; this.tbBlue.TickFrequency = 64; @@ -221,6 +224,7 @@ private void InitializeComponent() this.tbAlpha.Location = new System.Drawing.Point(0, 0); this.tbAlpha.Maximum = 255; this.tbAlpha.Name = "tbAlpha"; + this.tbAlpha.RightToLeftLayout = true; this.tbAlpha.Size = new System.Drawing.Size(78, 18); this.tbAlpha.TabIndex = 0; this.tbAlpha.TickFrequency = 64; @@ -259,13 +263,13 @@ private void InitializeComponent() this.colorDialog.AnyColor = true; this.colorDialog.FullOpen = true; // - // ucColorVisualizer + // ColorVisualizerControl // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Controls.Add(this.txtColor); this.Controls.Add(this.pnlControls); - this.Name = "ucColorVisualizer"; + this.Name = "ColorVisualizerControl"; this.Size = new System.Drawing.Size(247, 188); this.pnlControls.ResumeLayout(false); this.pnlControls.PerformLayout(); diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.Designer.cs index fe3cd9b..9666150 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.Designer.cs @@ -27,6 +27,7 @@ private void InitializeComponent() this.trackBar.Location = new System.Drawing.Point(35, 0); this.trackBar.Maximum = 100; this.trackBar.Name = "trackBar"; + this.trackBar.RightToLeftLayout = true; this.trackBar.Size = new System.Drawing.Size(147, 27); this.trackBar.TabIndex = 0; this.trackBar.TickFrequency = 10; diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs index 9c8d5d7..dc95106 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs @@ -65,6 +65,7 @@ internal DithererStrengthEditorControl(IWindowsFormsEditorService editorService, private DithererStrengthEditorControl() { + RightToLeft = LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft ? RightToLeft.Yes : RightToLeft.No; InitializeComponent(); } diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.Designer.cs index 8f7c3e9..109dd6e 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.Designer.cs @@ -28,6 +28,7 @@ private void InitializeComponent() this.trackBar.Location = new System.Drawing.Point(35, 0); this.trackBar.Maximum = 255; this.trackBar.Name = "trackBar"; + this.trackBar.RightToLeftLayout = true; this.trackBar.Size = new System.Drawing.Size(147, 27); this.trackBar.TabIndex = 0; this.trackBar.TickFrequency = 16; diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs index 6300631..cf741b5 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs @@ -63,6 +63,7 @@ internal QuantizerThresholdEditorControl(IWindowsFormsEditorService editorServic private QuantizerThresholdEditorControl() { + RightToLeft = LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft ? RightToLeft.Yes : RightToLeft.No; InitializeComponent(); } From 78d1b00ce3ac8b69eabcb86fd722f4cc2f13be13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 25 May 2021 16:03:24 +0200 Subject: [PATCH 046/211] Merging OK/Cancel buttons with Apply because an inherited control cannot scale the lately added button perfectly --- .../KGySoft.Drawing.ImagingTools.csproj | 6 -- .../View/Forms/EditResourcesForm.Designer.cs | 57 ++++++++++--------- .../Forms/LanguageSettingsForm.Designer.cs | 5 +- .../View/UserControls/OkCancelApplyButtons.cs | 55 ------------------ .../UserControls/OkCancelButtons.Designer.cs | 43 +++++++++----- .../View/UserControls/OkCancelButtons.cs | 31 +++++++++- 6 files changed, 91 insertions(+), 106 deletions(-) delete mode 100644 KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelApplyButtons.cs diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj index dd2e8ad..744e7c6 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj @@ -47,12 +47,6 @@ True Settings.settings - - Component - - - UserControl - diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index 685a809..446625d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -34,6 +34,9 @@ private void InitializeComponent() System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); this.gbResourceEntries = new System.Windows.Forms.GroupBox(); this.gridResources = new System.Windows.Forms.DataGridView(); + this.colResourceKey = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colOriginalText = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colTranslatedText = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.resourceEntryBindingSource = new System.Windows.Forms.BindingSource(this.components); this.gbResourceFile = new System.Windows.Forms.GroupBox(); this.cmbResourceFiles = new System.Windows.Forms.ComboBox(); @@ -43,10 +46,7 @@ private void InitializeComponent() this.txtOriginalText = new System.Windows.Forms.TextBox(); this.gbTranslatedText = new System.Windows.Forms.GroupBox(); this.txtTranslatedText = new System.Windows.Forms.TextBox(); - this.okCancelApplyButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelApplyButtons(); - this.colResourceKey = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colOriginalText = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colTranslatedText = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.okCancelApplyButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); this.gbResourceEntries.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.gridResources)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.resourceEntryBindingSource)).BeginInit(); @@ -94,6 +94,28 @@ private void InitializeComponent() this.gridResources.Size = new System.Drawing.Size(572, 93); this.gridResources.TabIndex = 3; // + // colResourceKey + // + this.colResourceKey.DataPropertyName = "Key"; + this.colResourceKey.HeaderText = "colResourceKey"; + this.colResourceKey.Name = "colResourceKey"; + this.colResourceKey.ReadOnly = true; + // + // colOriginalText + // + this.colOriginalText.DataPropertyName = "OriginalText"; + this.colOriginalText.HeaderText = "colOriginalText"; + this.colOriginalText.Name = "colOriginalText"; + this.colOriginalText.ReadOnly = true; + this.colOriginalText.Width = 200; + // + // colTranslatedText + // + this.colTranslatedText.DataPropertyName = "TranslatedText"; + this.colTranslatedText.HeaderText = "colTranslatedText"; + this.colTranslatedText.Name = "colTranslatedText"; + this.colTranslatedText.Width = 200; + // // resourceEntryBindingSource // this.resourceEntryBindingSource.DataSource = typeof(KGySoft.Drawing.ImagingTools.Model.ResourceEntry); @@ -191,36 +213,15 @@ private void InitializeComponent() this.txtTranslatedText.TabIndex = 1; this.txtTranslatedText.WordWrap = false; // - // okCancelButtons + // okCancelApplyButtons // + this.okCancelApplyButtons.ApplyButtonVisible = true; this.okCancelApplyButtons.Dock = System.Windows.Forms.DockStyle.Bottom; this.okCancelApplyButtons.Location = new System.Drawing.Point(3, 268); this.okCancelApplyButtons.Name = "okCancelApplyButtons"; this.okCancelApplyButtons.Size = new System.Drawing.Size(578, 40); this.okCancelApplyButtons.TabIndex = 1; // - // colResourceKey - // - this.colResourceKey.DataPropertyName = "Key"; - this.colResourceKey.HeaderText = "colResourceKey"; - this.colResourceKey.Name = "colResourceKey"; - this.colResourceKey.ReadOnly = true; - // - // colOriginalText - // - this.colOriginalText.DataPropertyName = "OriginalText"; - this.colOriginalText.HeaderText = "colOriginalText"; - this.colOriginalText.Name = "colOriginalText"; - this.colOriginalText.ReadOnly = true; - this.colOriginalText.Width = 200; - // - // colTranslatedText - // - this.colTranslatedText.DataPropertyName = "TranslatedText"; - this.colTranslatedText.HeaderText = "colTranslatedText"; - this.colTranslatedText.Name = "colTranslatedText"; - this.colTranslatedText.Width = 200; - // // EditResourcesForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -251,7 +252,7 @@ private void InitializeComponent() #endregion - private UserControls.OkCancelApplyButtons okCancelApplyButtons; + private UserControls.OkCancelButtons okCancelApplyButtons; private System.Windows.Forms.GroupBox gbResourceEntries; private System.Windows.Forms.DataGridView gridResources; private System.Windows.Forms.TableLayoutPanel pnlEditResourceEntry; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs index a27cb25..4b8f6ca 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs @@ -30,7 +30,7 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - this.okCancelApplyButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelApplyButtons(); + this.okCancelApplyButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); this.gbAllowResxResources = new KGySoft.Drawing.ImagingTools.View.Controls.CheckGroupBox(); this.gbDisplayLanguage = new System.Windows.Forms.GroupBox(); this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); @@ -46,6 +46,7 @@ private void InitializeComponent() // // okCancelApplyButtons // + this.okCancelApplyButtons.ApplyButtonVisible = true; this.okCancelApplyButtons.Dock = System.Windows.Forms.DockStyle.Bottom; this.okCancelApplyButtons.Location = new System.Drawing.Point(3, 108); this.okCancelApplyButtons.Name = "okCancelApplyButtons"; @@ -165,7 +166,7 @@ private void InitializeComponent() #endregion - private UserControls.OkCancelApplyButtons okCancelApplyButtons; + private UserControls.OkCancelButtons okCancelApplyButtons; private KGySoft.Drawing.ImagingTools.View.Controls.CheckGroupBox gbAllowResxResources; private System.Windows.Forms.CheckBox chbExistingResourcesOnly; private System.Windows.Forms.ToolTip toolTip; diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelApplyButtons.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelApplyButtons.cs deleted file mode 100644 index 4e6e9f5..0000000 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelApplyButtons.cs +++ /dev/null @@ -1,55 +0,0 @@ -#region Copyright - -/////////////////////////////////////////////////////////////////////////////// -// File: OkCancelApplyButtons.cs -/////////////////////////////////////////////////////////////////////////////// -// Copyright (C) KGy SOFT, 2005-2020 - All Rights Reserved -// -// You should have received a copy of the LICENSE file at the top-level -// directory of this distribution. If not, then this file is considered as -// an illegal copy. -// -// Unauthorized copying of this file, via any medium is strictly prohibited. -/////////////////////////////////////////////////////////////////////////////// - -#endregion - -#region Usings - -using System.Windows.Forms; - -#endregion - -namespace KGySoft.Drawing.ImagingTools.View.UserControls -{ - internal class OkCancelApplyButtons : OkCancelButtons - { - #region Properties - - internal Button ApplyButton { get; } - - #endregion - - #region Constructors - - public OkCancelApplyButtons() - { - pnlButtons.SuspendLayout(); - ApplyButton = new Button - { - Anchor = AnchorStyles.None, - FlatStyle = FlatStyle.System, - Name = "btnApply", - TabIndex = 2, - Text = @"btnApply", - UseVisualStyleBackColor = true - - }; - pnlButtons.Controls.Add(ApplyButton); - ApplyButton.BringToFront(); - pnlButtons.ResumeLayout(false); - } - - #endregion - } -} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs index 6194cd5..be00b01 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs @@ -16,13 +16,15 @@ partial class OkCancelButtons private void InitializeComponent() { this.pnlButtons = new System.Windows.Forms.FlowLayoutPanel(); - this.btnOK = new System.Windows.Forms.Button(); this.btnCancel = new System.Windows.Forms.Button(); + this.btnOK = new System.Windows.Forms.Button(); + this.btnApply = new System.Windows.Forms.Button(); this.pnlButtons.SuspendLayout(); this.SuspendLayout(); // // pnlButtons // + this.pnlButtons.Controls.Add(this.btnApply); this.pnlButtons.Controls.Add(this.btnCancel); this.pnlButtons.Controls.Add(this.btnOK); this.pnlButtons.Dock = System.Windows.Forms.DockStyle.Fill; @@ -30,32 +32,44 @@ private void InitializeComponent() this.pnlButtons.Location = new System.Drawing.Point(0, 0); this.pnlButtons.Name = "pnlButtons"; this.pnlButtons.Padding = new System.Windows.Forms.Padding(3); - this.pnlButtons.Size = new System.Drawing.Size(218, 40); + this.pnlButtons.Size = new System.Drawing.Size(260, 40); this.pnlButtons.TabIndex = 0; // + // btnCancel + // + this.btnCancel.Anchor = System.Windows.Forms.AnchorStyles.None; + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.btnCancel.Location = new System.Drawing.Point(95, 6); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.TabIndex = 1; + this.btnCancel.Text = "btnCancel"; + this.btnCancel.UseVisualStyleBackColor = true; + // // btnOK // this.btnOK.Anchor = System.Windows.Forms.AnchorStyles.None; this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK; this.btnOK.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.btnOK.Location = new System.Drawing.Point(53, 6); + this.btnOK.Location = new System.Drawing.Point(14, 6); this.btnOK.Name = "btnOK"; this.btnOK.Size = new System.Drawing.Size(75, 23); this.btnOK.TabIndex = 0; this.btnOK.Text = "btnOK"; this.btnOK.UseVisualStyleBackColor = true; // - // btnCancel + // btnApply // - this.btnCancel.Anchor = System.Windows.Forms.AnchorStyles.None; - this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.btnCancel.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.btnCancel.Location = new System.Drawing.Point(134, 6); - this.btnCancel.Name = "btnCancel"; - this.btnCancel.Size = new System.Drawing.Size(75, 23); - this.btnCancel.TabIndex = 1; - this.btnCancel.Text = "btnCancel"; - this.btnCancel.UseVisualStyleBackColor = true; + this.btnApply.Anchor = System.Windows.Forms.AnchorStyles.None; + this.btnApply.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.btnApply.Location = new System.Drawing.Point(176, 6); + this.btnApply.Name = "btnApply"; + this.btnApply.Size = new System.Drawing.Size(75, 23); + this.btnApply.TabIndex = 2; + this.btnApply.Text = "btnApply"; + this.btnApply.UseVisualStyleBackColor = true; + this.btnApply.Visible = false; // // OkCancelButtons // @@ -63,7 +77,7 @@ private void InitializeComponent() this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Controls.Add(this.pnlButtons); this.Name = "OkCancelButtons"; - this.Size = new System.Drawing.Size(218, 40); + this.Size = new System.Drawing.Size(260, 40); this.pnlButtons.ResumeLayout(false); this.ResumeLayout(false); @@ -73,5 +87,6 @@ private void InitializeComponent() private System.Windows.Forms.Button btnOK; private System.Windows.Forms.Button btnCancel; protected System.Windows.Forms.FlowLayoutPanel pnlButtons; + private System.Windows.Forms.Button btnApply; } } diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.cs index 41ea9e4..1b14464 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.cs @@ -16,18 +16,46 @@ #region Usings +using System.ComponentModel; using System.Windows.Forms; #endregion namespace KGySoft.Drawing.ImagingTools.View.UserControls { - internal partial class OkCancelButtons : BaseUserControl + internal sealed partial class OkCancelButtons : BaseUserControl { + #region Fields + + private bool isApplyVisible; + + #endregion + #region Properties + #region Public Properties + + [DefaultValue(false)] + public bool ApplyButtonVisible + { + get => isApplyVisible; + set + { + if (isApplyVisible == value) + return; + ApplyButton.Visible = isApplyVisible = value; + } + } + + #endregion + + #region Internal Properties + internal Button OKButton => btnOK; internal Button CancelButton => btnCancel; + internal Button ApplyButton => btnApply; + + #endregion #endregion @@ -35,6 +63,7 @@ internal partial class OkCancelButtons : BaseUserControl public OkCancelButtons() { + AutoScaleMode = AutoScaleMode.Inherit; InitializeComponent(); } From 5cb4ece3da28e0336cd3a0eb57d681709e6323c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 25 May 2021 17:04:41 +0200 Subject: [PATCH 047/211] Making internal dialog windows right-to-left compatible --- KGySoft.Drawing.ImagingTools/View/Dialogs.cs | 20 +++++++++++++++---- .../ViewModel/ViewModelBase.cs | 4 ++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs index 9d66992..1c42dd0 100644 --- a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs +++ b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs @@ -26,10 +26,22 @@ internal static class Dialogs { #region Methods - public static void ErrorMessage(string message) => MessageBox.Show(message, Res.TitleError, MessageBoxButtons.OK, MessageBoxIcon.Error); - public static void InfoMessage(string message) => MessageBox.Show(message, Res.TitleInformation, MessageBoxButtons.OK, MessageBoxIcon.Information); - public static void WarningMessage(string message) => MessageBox.Show(message, Res.TitleWarning, MessageBoxButtons.OK, MessageBoxIcon.Warning); - public static bool ConfirmMessage(string message) => MessageBox.Show(message, Res.TitleConfirmation, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes; + #region Internal Methods + + internal static void ErrorMessage(string message) => Show(message, Res.TitleError, MessageBoxButtons.OK, MessageBoxIcon.Error); + internal static void InfoMessage(string message) => Show(message, Res.TitleInformation, MessageBoxButtons.OK, MessageBoxIcon.Information); + internal static void WarningMessage(string message) => Show(message, Res.TitleWarning, MessageBoxButtons.OK, MessageBoxIcon.Warning); + internal static bool ConfirmMessage(string message, bool isYesDefault = true) + => Show(message, Res.TitleConfirmation, MessageBoxButtons.YesNo, MessageBoxIcon.Question, isYesDefault ? MessageBoxDefaultButton.Button1 : MessageBoxDefaultButton.Button2) == DialogResult.Yes; + + #endregion + + #region Private Methods + + private static DialogResult Show(string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1) + => MessageBox.Show(message, caption, buttons, icon, defaultButton, LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft ? MessageBoxOptions.RightAlign | MessageBoxOptions.RtlReading : default); + + #endregion #endregion } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs index 256505a..85366a9 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs @@ -34,7 +34,7 @@ internal abstract class ViewModelBase : ObservableObjectBase, IViewModel internal Action? ShowErrorCallback { get => Get?>(); set => Set(value); } internal Action? ShowWarningCallback { get => Get?>(); set => Set(value); } internal Action? ShowInfoCallback { get => Get?>(); set => Set(value); } - internal Func? ConfirmCallback { get => Get?>(); set => Set(value); } + internal Func? ConfirmCallback { get => Get?>(); set => Set(value); } internal Action? ShowChildViewCallback { get => Get?>(); set => Set(value); } internal Action? CloseViewCallback { get => Get(); set => Set(value); } internal Action? SynchronizedInvokeCallback { private get => Get?>(); set => Set(value); } @@ -54,7 +54,7 @@ internal abstract class ViewModelBase : ObservableObjectBase, IViewModel protected void ShowError(string message) => ShowErrorCallback?.Invoke(message); protected void ShowWarning(string message) => ShowWarningCallback?.Invoke(message); protected void ShowInfo(string message) => ShowInfoCallback?.Invoke(message); - protected bool Confirm(string message) => ConfirmCallback?.Invoke(message) ?? true; + protected bool Confirm(string message, bool isYesDefault = true) => ConfirmCallback?.Invoke(message, isYesDefault) ?? true; protected bool TryInvokeSync(Action action) { From fbd240c2fad91a4d9887961c15aa9cc8784c895b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 25 May 2021 19:50:50 +0200 Subject: [PATCH 048/211] Moving components to Components namespace, supporting right-to-left ToolTips --- .../AdvancedToolStripSplitButton.cs | 2 +- .../View/Components/AdvancedToolTip.cs | 82 +++++++++++++++++++ .../ScalingToolStripDropDownButton.cs | 2 +- .../ZoomSplitButton.cs | 2 +- .../View/Controls/ScalingToolStrip.cs | 2 +- .../Forms/ImageVisualizerForm.Designer.cs | 13 +-- .../View/Forms/MvvmBaseForm.Designer.cs | 4 +- .../PreviewImageControl.Designer.cs | 8 +- 8 files changed, 100 insertions(+), 15 deletions(-) rename KGySoft.Drawing.ImagingTools/View/{Controls => Components}/AdvancedToolStripSplitButton.cs (98%) create mode 100644 KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs rename KGySoft.Drawing.ImagingTools/View/{Controls => Components}/ScalingToolStripDropDownButton.cs (98%) rename KGySoft.Drawing.ImagingTools/View/{Controls => Components}/ZoomSplitButton.cs (98%) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStripSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs similarity index 98% rename from KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStripSplitButton.cs rename to KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs index fb027d6..af1cf3d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStripSplitButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs @@ -23,7 +23,7 @@ #endregion -namespace KGySoft.Drawing.ImagingTools.View.Controls +namespace KGySoft.Drawing.ImagingTools.View.Components { /// /// A whose button part can be checked. diff --git a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs new file mode 100644 index 0000000..a39c183 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs @@ -0,0 +1,82 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: AdvancedToolTip.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View.Components +{ + /// + /// A ToolTip that supports RTL correctly + /// + /// + internal class AdvancedToolTip : ToolTip + { + #region Constructors + + public AdvancedToolTip() + { + OwnerDraw = true; + Draw += AdvancedToolTip_Draw; + } + + public AdvancedToolTip(IContainer container) : base(container) + { + OwnerDraw = true; + Draw += AdvancedToolTip_Draw; + } + + #endregion + + #region Methods + + #region Static Methods + + private static void AdvancedToolTip_Draw(object sender, DrawToolTipEventArgs e) + { + // same as DrawBackground but will not recreate the brush again and again + e.Graphics.FillRectangle(SystemBrushes.Info, e.Bounds); + e.DrawBorder(); + + var flags = TextFormatFlags.HidePrefix | TextFormatFlags.VerticalCenter; + if (LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft) + flags |= TextFormatFlags.RightToLeft | TextFormatFlags.Right; + + e.DrawText(flags); + } + + #endregion + + #region Instance Methods + + protected override void Dispose(bool disposing) + { + if (disposing) + Draw -= AdvancedToolTip_Draw; + + base.Dispose(disposing); + } + + #endregion + + #endregion + } +} diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripDropDownButton.cs b/KGySoft.Drawing.ImagingTools/View/Components/ScalingToolStripDropDownButton.cs similarity index 98% rename from KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripDropDownButton.cs rename to KGySoft.Drawing.ImagingTools/View/Components/ScalingToolStripDropDownButton.cs index 2f835f0..bb670b2 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStripDropDownButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/ScalingToolStripDropDownButton.cs @@ -21,7 +21,7 @@ #endregion -namespace KGySoft.Drawing.ImagingTools.View.Controls +namespace KGySoft.Drawing.ImagingTools.View.Components { /// /// A that can scale its arrow regardless of .NET version and app.config settings. diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ZoomSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs similarity index 98% rename from KGySoft.Drawing.ImagingTools/View/Controls/ZoomSplitButton.cs rename to KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs index 16fb5f0..53f0c61 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ZoomSplitButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs @@ -21,7 +21,7 @@ #endregion -namespace KGySoft.Drawing.ImagingTools.View.Controls +namespace KGySoft.Drawing.ImagingTools.View.Components { internal class ZoomSplitButton : AdvancedToolStripSplitButton { diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs index a7c970b..eed2478 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs @@ -20,7 +20,7 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; - +using KGySoft.Drawing.ImagingTools.View.Components; using KGySoft.Drawing.ImagingTools.WinApi; #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs index 932fb42..b147535 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs @@ -1,4 +1,5 @@ using System.Windows.Forms; +using KGySoft.Drawing.ImagingTools.View.Components; using KGySoft.Drawing.ImagingTools.View.Controls; namespace KGySoft.Drawing.ImagingTools.View.Forms @@ -27,21 +28,21 @@ private void InitializeComponent() this.lblNotification = new KGySoft.Drawing.ImagingTools.View.Controls.NotificationLabel(); this.splitter = new System.Windows.Forms.Splitter(); this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStrip(); - this.btnZoom = new KGySoft.Drawing.ImagingTools.View.Controls.ZoomSplitButton(); + this.btnZoom = new ZoomSplitButton(); this.btnAntiAlias = new System.Windows.Forms.ToolStripButton(); this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); this.btnOpen = new System.Windows.Forms.ToolStripButton(); this.btnSave = new System.Windows.Forms.ToolStripButton(); this.btnClear = new System.Windows.Forms.ToolStripButton(); this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); - this.btnColorSettings = new KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStripDropDownButton(); + this.btnColorSettings = new ScalingToolStripDropDownButton(); this.miBackColor = new System.Windows.Forms.ToolStripMenuItem(); this.miBackColorDefault = new System.Windows.Forms.ToolStripMenuItem(); this.miBackColorWhite = new System.Windows.Forms.ToolStripMenuItem(); this.miBackColorBlack = new System.Windows.Forms.ToolStripMenuItem(); this.miShowPalette = new System.Windows.Forms.ToolStripMenuItem(); this.miCountColors = new System.Windows.Forms.ToolStripMenuItem(); - this.btnEdit = new KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStripDropDownButton(); + this.btnEdit = new ScalingToolStripDropDownButton(); this.miRotateLeft = new System.Windows.Forms.ToolStripMenuItem(); this.miRotateRight = new System.Windows.Forms.ToolStripMenuItem(); this.miResizeBitmap = new System.Windows.Forms.ToolStripMenuItem(); @@ -56,7 +57,7 @@ private void InitializeComponent() this.btnPrev = new System.Windows.Forms.ToolStripButton(); this.btnNext = new System.Windows.Forms.ToolStripButton(); this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); - this.btnConfiguration = new KGySoft.Drawing.ImagingTools.View.Controls.AdvancedToolStripSplitButton(); + this.btnConfiguration = new AdvancedToolStripSplitButton(); this.txtInfo = new System.Windows.Forms.TextBox(); this.miManageInstallations = new System.Windows.Forms.ToolStripMenuItem(); this.miLanguageSettings = new System.Windows.Forms.ToolStripMenuItem(); @@ -400,7 +401,7 @@ private void InitializeComponent() #endregion private KGySoft.Drawing.ImagingTools.View.Controls.ImageViewer imageViewer; - private KGySoft.Drawing.ImagingTools.View.Controls.ZoomSplitButton btnZoom; + private ZoomSplitButton btnZoom; private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; private System.Windows.Forms.ToolStripButton btnSave; private System.Windows.Forms.ToolStripButton btnOpen; @@ -413,7 +414,7 @@ private void InitializeComponent() private System.Windows.Forms.SaveFileDialog dlgSave; private System.Windows.Forms.Splitter splitter; private System.Windows.Forms.Timer timerPlayer; - private KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStripDropDownButton btnColorSettings; + private ScalingToolStripDropDownButton btnColorSettings; private System.Windows.Forms.ToolStripMenuItem miBackColor; private System.Windows.Forms.ToolStripMenuItem miBackColorDefault; private System.Windows.Forms.ToolStripMenuItem miBackColorWhite; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs index e4b1779..92b5839 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs @@ -2,13 +2,13 @@ { partial class MvvmBaseForm { - private System.Windows.Forms.ToolTip toolTip; + private KGySoft.Drawing.ImagingTools.View.Components.AdvancedToolTip toolTip; private System.ComponentModel.IContainer components; private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - this.toolTip = new System.Windows.Forms.ToolTip(this.components); + this.toolTip = new KGySoft.Drawing.ImagingTools.View.Components.AdvancedToolTip(this.components); this.SuspendLayout(); // // toolTip diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs index 883f783..596e85c 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs @@ -1,4 +1,6 @@ -namespace KGySoft.Drawing.ImagingTools.View.UserControls +using KGySoft.Drawing.ImagingTools.View.Components; + +namespace KGySoft.Drawing.ImagingTools.View.UserControls { partial class PreviewImageControl { @@ -18,7 +20,7 @@ private void InitializeComponent() System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PreviewImageControl)); this.ivPreview = new KGySoft.Drawing.ImagingTools.View.Controls.ImageViewer(); this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStrip(); - this.btnZoom = new KGySoft.Drawing.ImagingTools.View.Controls.ZoomSplitButton(); + this.btnZoom = new ZoomSplitButton(); this.btnAntiAlias = new System.Windows.Forms.ToolStripButton(); this.btnShowOriginal = new System.Windows.Forms.ToolStripButton(); this.tsMenu.SuspendLayout(); @@ -85,7 +87,7 @@ private void InitializeComponent() #endregion private Controls.ScalingToolStrip tsMenu; - private Controls.ZoomSplitButton btnZoom; + private ZoomSplitButton btnZoom; private System.Windows.Forms.ToolStripButton btnAntiAlias; private Controls.ImageViewer ivPreview; private System.Windows.Forms.ToolStripButton btnShowOriginal; From 763a5177b6d65492bc5306f613114493f763742a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 25 May 2021 20:35:14 +0200 Subject: [PATCH 049/211] Adding right-to-left support for ToolStrip tool tips. --- .../View/Components/AdvancedToolTip.cs | 13 +----- ...alingToolStrip.cs => AdvancedToolStrip.cs} | 41 +++++++++++++---- .../Forms/ImageVisualizerForm.Designer.cs | 4 +- .../ColorVisualizerControl.Designer.cs | 4 +- .../PreviewImageControl.Designer.cs | 4 +- .../DrawToolTipEventArgsExtensions.cs | 46 +++++++++++++++++++ 6 files changed, 85 insertions(+), 27 deletions(-) rename KGySoft.Drawing.ImagingTools/View/Controls/{ScalingToolStrip.cs => AdvancedToolStrip.cs} (89%) create mode 100644 KGySoft.Drawing.ImagingTools/View/_Extensions/DrawToolTipEventArgsExtensions.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs index a39c183..4503f15 100644 --- a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs @@ -50,18 +50,7 @@ public AdvancedToolTip(IContainer container) : base(container) #region Static Methods - private static void AdvancedToolTip_Draw(object sender, DrawToolTipEventArgs e) - { - // same as DrawBackground but will not recreate the brush again and again - e.Graphics.FillRectangle(SystemBrushes.Info, e.Bounds); - e.DrawBorder(); - - var flags = TextFormatFlags.HidePrefix | TextFormatFlags.VerticalCenter; - if (LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft) - flags |= TextFormatFlags.RightToLeft | TextFormatFlags.Right; - - e.DrawText(flags); - } + private static void AdvancedToolTip_Draw(object sender, DrawToolTipEventArgs e) => e.DrawToolTipAdvanced(); #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs similarity index 89% rename from KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs rename to KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index eed2478..031c55f 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -1,7 +1,7 @@ #region Copyright /////////////////////////////////////////////////////////////////////////////// -// File: ScalingToolStrip.cs +// File: AdvancedToolStrip.cs /////////////////////////////////////////////////////////////////////////////// // Copyright (C) KGy SOFT, 2005-2019 - All Rights Reserved // @@ -22,18 +22,20 @@ using System.Windows.Forms; using KGySoft.Drawing.ImagingTools.View.Components; using KGySoft.Drawing.ImagingTools.WinApi; +using KGySoft.Reflection; #endregion namespace KGySoft.Drawing.ImagingTools.View.Controls { /// - /// A that can scale its content regardless of .NET version and app.config settings. + /// A with some additional features: + /// - It can scale its content regardless of .NET version and app.config settings. + /// - Custom renderer for checked state and scaled arrows. + /// - Tool tip supports right-to-left /// - internal class ScalingToolStrip : ToolStrip + internal class AdvancedToolStrip : ToolStrip { - #region Nested classes - #region ScalingToolStripMenuRenderer class private class ScalingToolStripMenuRenderer : ToolStripProfessionalRenderer @@ -177,8 +179,6 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr #endregion - #endregion - #region Fields #region Static Fields @@ -189,6 +189,8 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr #region Instance Fields + private readonly ToolTip toolTip; + private DockStyle explicitDock = DockStyle.Top; private bool isAdjustingRtl; @@ -198,16 +200,28 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr #region Constructors - public ScalingToolStrip() + public AdvancedToolStrip() { ImageScalingSize = Size.Round(this.ScaleSize(referenceSize)); Renderer = new ScalingToolStripMenuRenderer(); + toolTip = (ToolTip)Reflector.GetProperty(this, nameof(ToolTip))!; + toolTip.AutoPopDelay = 10_000; + toolTip.OwnerDraw = true; + toolTip.Draw += ToolTip_Draw; } #endregion #region Methods + #region Static Methods + + private static void ToolTip_Draw(object sender, DrawToolTipEventArgs e) => e.DrawToolTipAdvanced(); + + #endregion + + #region Instance Methods + protected override void WndProc(ref Message m) { base.WndProc(ref m); @@ -250,6 +264,15 @@ protected override void OnRightToLeftChanged(EventArgs e) isAdjustingRtl = false; } - #endregion + protected override void Dispose(bool disposing) + { + if (disposing) + toolTip.Draw -= ToolTip_Draw; + base.Dispose(disposing); + } + + #endregion + + #endregion } } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs index b147535..fe28052 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs @@ -27,7 +27,7 @@ private void InitializeComponent() this.imageViewer = new KGySoft.Drawing.ImagingTools.View.Controls.ImageViewer(); this.lblNotification = new KGySoft.Drawing.ImagingTools.View.Controls.NotificationLabel(); this.splitter = new System.Windows.Forms.Splitter(); - this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStrip(); + this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.AdvancedToolStrip(); this.btnZoom = new ZoomSplitButton(); this.btnAntiAlias = new System.Windows.Forms.ToolStripButton(); this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); @@ -420,7 +420,7 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem miBackColorWhite; private System.Windows.Forms.ToolStripMenuItem miBackColorBlack; private System.Windows.Forms.ToolStripMenuItem miShowPalette; - protected KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStrip tsMenu; + protected KGySoft.Drawing.ImagingTools.View.Controls.AdvancedToolStrip tsMenu; protected System.Windows.Forms.TextBox txtInfo; private KGySoft.Drawing.ImagingTools.View.Controls.NotificationLabel lblNotification; private ToolStripSeparator toolStripSeparator2; diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs index 874abae..c757b22 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs @@ -32,7 +32,7 @@ private void InitializeComponent() this.lblAlpha = new System.Windows.Forms.Label(); this.pnlAlpha = new System.Windows.Forms.Panel(); this.tbAlpha = new System.Windows.Forms.TrackBar(); - this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStrip(); + this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.AdvancedToolStrip(); this.btnSelectColor = new System.Windows.Forms.ToolStripButton(); this.txtColor = new System.Windows.Forms.TextBox(); this.colorDialog = new System.Windows.Forms.ColorDialog(); @@ -297,7 +297,7 @@ private void InitializeComponent() #endregion private System.Windows.Forms.Panel pnlControls; - private ScalingToolStrip tsMenu; + private AdvancedToolStrip tsMenu; private System.Windows.Forms.TableLayoutPanel tblColor; private System.Windows.Forms.Label lblRed; private System.Windows.Forms.Panel pnlRed; diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs index 596e85c..83e4f56 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.Designer.cs @@ -19,7 +19,7 @@ private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PreviewImageControl)); this.ivPreview = new KGySoft.Drawing.ImagingTools.View.Controls.ImageViewer(); - this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.ScalingToolStrip(); + this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.AdvancedToolStrip(); this.btnZoom = new ZoomSplitButton(); this.btnAntiAlias = new System.Windows.Forms.ToolStripButton(); this.btnShowOriginal = new System.Windows.Forms.ToolStripButton(); @@ -86,7 +86,7 @@ private void InitializeComponent() #endregion - private Controls.ScalingToolStrip tsMenu; + private Controls.AdvancedToolStrip tsMenu; private ZoomSplitButton btnZoom; private System.Windows.Forms.ToolStripButton btnAntiAlias; private Controls.ImageViewer ivPreview; diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/DrawToolTipEventArgsExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/DrawToolTipEventArgsExtensions.cs new file mode 100644 index 0000000..f406d1d --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/DrawToolTipEventArgsExtensions.cs @@ -0,0 +1,46 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: DrawToolTipEventArgsExtensions.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System.Drawing; +using System.Windows.Forms; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View +{ + internal static class DrawToolTipEventArgsExtensions + { + #region Methods + + internal static void DrawToolTipAdvanced(this DrawToolTipEventArgs e) + { + // Same as DrawBackground but will not recreate the brush again and again + // Note: the background color of this tool tip may differ from default ToolTip but will be the same as the ones on Close/Minimize/Maximize buttons + e.Graphics.FillRectangle(SystemBrushes.Info, e.Bounds); + e.DrawBorder(); + + var flags = TextFormatFlags.HidePrefix | TextFormatFlags.VerticalCenter | TextFormatFlags.LeftAndRightPadding; + if (LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft) + flags |= TextFormatFlags.RightToLeft | TextFormatFlags.Right; + + e.DrawText(flags); + } + + #endregion + } +} \ No newline at end of file From 7a4d855f51f1f2dde3e0aa8550126e6a6af57463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 26 May 2021 11:09:27 +0200 Subject: [PATCH 050/211] Applying right to left to ErrorProviders --- .../View/Components/AdvancedToolTip.cs | 1 - .../View/Forms/TransformBitmapFormBase.cs | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs index 4503f15..7957357 100644 --- a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs @@ -17,7 +17,6 @@ #region Usings using System.ComponentModel; -using System.Drawing; using System.Windows.Forms; #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs index e812a00..479c47d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs @@ -143,6 +143,10 @@ private void InitPropertyBindings() // VM.Progress -> progress.Progress CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Progress), nameof(progress.Progress), progress); + + // this.RightToLeft -> errorProvider/warningProvider/infoProvider.RightToLeft + CommandBindings.AddPropertyBinding(this, nameof(RightToLeft), nameof(ErrorProvider.RightToLeft), + rtl => rtl is RightToLeft.Yes, errorProvider, warningProvider, infoProvider); } #endregion From e802069663bc06799ef5469a1a9b364e8895d7fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 26 May 2021 13:45:36 +0200 Subject: [PATCH 051/211] Fixing custom AdvancedToolStrip tool tip for Mono --- .../View/Controls/AdvancedToolStrip.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 031c55f..8d5b94f 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -33,6 +33,7 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls /// - It can scale its content regardless of .NET version and app.config settings. /// - Custom renderer for checked state and scaled arrows. /// - Tool tip supports right-to-left + /// - Clicking works even if the owner form was not active /// internal class AdvancedToolStrip : ToolStrip { @@ -189,7 +190,7 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr #region Instance Fields - private readonly ToolTip toolTip; + private readonly ToolTip? toolTip; private DockStyle explicitDock = DockStyle.Top; private bool isAdjustingRtl; @@ -204,6 +205,13 @@ public AdvancedToolStrip() { ImageScalingSize = Size.Round(this.ScaleSize(referenceSize)); Renderer = new ScalingToolStripMenuRenderer(); + + toolTip = Reflector.TryGetProperty(this, nameof(ToolTip), out object? result) ? (ToolTip)result! + : Reflector.TryGetField(this, "tooltip_window", out result) ? (ToolTip)result! + : null; + + if (toolTip == null) + return; toolTip = (ToolTip)Reflector.GetProperty(this, nameof(ToolTip))!; toolTip.AutoPopDelay = 10_000; toolTip.OwnerDraw = true; @@ -267,7 +275,11 @@ protected override void OnRightToLeftChanged(EventArgs e) protected override void Dispose(bool disposing) { if (disposing) - toolTip.Draw -= ToolTip_Draw; + { + if (toolTip != null) + toolTip.Draw -= ToolTip_Draw; + } + base.Dispose(disposing); } From 695cdfad2f9e7ea50c69f088d6b0aff61c6d155d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 26 May 2021 17:42:58 +0200 Subject: [PATCH 052/211] ToolTip: Using custom drawing only for RTL languages to tool tips can use system theme when possible --- .../View/Components/AdvancedToolTip.cs | 35 +++++++++++++------ .../View/Controls/AdvancedToolStrip.cs | 12 ++++--- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs index 7957357..9346136 100644 --- a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs @@ -16,6 +16,7 @@ #region Usings +using System; using System.ComponentModel; using System.Windows.Forms; @@ -31,17 +32,9 @@ internal class AdvancedToolTip : ToolTip { #region Constructors - public AdvancedToolTip() - { - OwnerDraw = true; - Draw += AdvancedToolTip_Draw; - } + public AdvancedToolTip() => Initialize(); - public AdvancedToolTip(IContainer container) : base(container) - { - OwnerDraw = true; - Draw += AdvancedToolTip_Draw; - } + public AdvancedToolTip(IContainer container) : base(container) => Initialize(); #endregion @@ -58,13 +51,35 @@ public AdvancedToolTip(IContainer container) : base(container) protected override void Dispose(bool disposing) { if (disposing) + { Draw -= AdvancedToolTip_Draw; + LanguageSettings.DisplayLanguageChanged -= LanguageSettings_DisplayLanguageChanged; + } base.Dispose(disposing); } #endregion + #region Private Methods + + private void Initialize() + { + LanguageSettings.DisplayLanguageChanged += LanguageSettings_DisplayLanguageChanged; + Draw += AdvancedToolTip_Draw; + ResetOwnerDraw(); + } + + private void ResetOwnerDraw() => OwnerDraw = LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft; + + #endregion + + #region Event Handlers + + private void LanguageSettings_DisplayLanguageChanged(object sender, EventArgs e) => ResetOwnerDraw(); + + #endregion + #endregion } } diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 8d5b94f..8ca592f 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -214,7 +214,8 @@ public AdvancedToolStrip() return; toolTip = (ToolTip)Reflector.GetProperty(this, nameof(ToolTip))!; toolTip.AutoPopDelay = 10_000; - toolTip.OwnerDraw = true; + + // Effectively used only when RTL is true because OwnerDraw is enabled only in that case toolTip.Draw += ToolTip_Draw; } @@ -258,15 +259,18 @@ protected override void OnDockChanged(EventArgs e) protected override void OnRightToLeftChanged(EventArgs e) { base.OnRightToLeftChanged(e); + bool isRtl = RightToLeft == RightToLeft.Yes; + if (toolTip != null) + toolTip.OwnerDraw = isRtl; + DockStyle dock = Dock; if (dock is not (DockStyle.Left or DockStyle.Right)) return; - bool isMirrored = RightToLeft == RightToLeft.Yes; - if (isMirrored ^ dock == explicitDock) + if (isRtl ^ dock == explicitDock) return; isAdjustingRtl = true; - Dock = isMirrored + Dock = isRtl ? explicitDock == DockStyle.Left ? DockStyle.Right : DockStyle.Left : explicitDock; isAdjustingRtl = false; From d69c802701adeebc0aa92e009cdf8d324a04cdac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 26 May 2021 18:34:52 +0200 Subject: [PATCH 053/211] Fixing rendering and behavior --- .../View/Components/AdvancedToolStripSplitButton.cs | 5 ++++- .../View/Controls/AdvancedToolStrip.cs | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs index af1cf3d..b601ba8 100644 --- a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs @@ -116,7 +116,10 @@ protected override void OnButtonClick(EventArgs e) { if (CheckOnClick) Checked = !Checked; - base.OnButtonClick(e); + if (OSUtils.IsWindows) + base.OnButtonClick(e); + else + DefaultItem?.PerformClick(); } protected virtual void OnCheckedChanged(EventArgs e) => (Events[nameof(CheckedChanged)] as EventHandler)?.Invoke(this, e); diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 8ca592f..f0d821b 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -152,6 +152,8 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr Rectangle rect = btn.ButtonBounds; if (!OSUtils.IsWindows) rect.Location = Point.Empty; + else if (e.Item.RightToLeft == RightToLeft.Yes) + rect.Offset(-1, 0); if (btn.Enabled && (btn.Checked || !btn.DropDownButtonPressed)) { From 2ba53df218d9faa8560af8b24b550aaaf3936532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 26 May 2021 18:35:16 +0200 Subject: [PATCH 054/211] Refining validation conditions --- KGySoft.Drawing.ImagingTools/ViewModel/ColorSpaceViewModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ColorSpaceViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ColorSpaceViewModel.cs index 2f8ebf3..9419cd1 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ColorSpaceViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ColorSpaceViewModel.cs @@ -227,9 +227,9 @@ protected override ValidationResultsCollection DoValidation() { if (bpp <= 8 && bpp < originalBpp) result.AddInfo(nameof(QuantizerSelectorViewModel.Quantizer), Res.InfoMessagePaletteAutoSelected(1 << bpp, pixelFormat)); - else if (ditherer != null && pixelFormat.CanBeDithered()) + else if (changePixelFormat && ditherer != null && pixelFormat.CanBeDithered() && !(bpp <= 8 && bpp >= originalBpp)) result.AddInfo(nameof(QuantizerSelectorViewModel.Quantizer), Res.InfoMessageQuantizerAutoSelected(pixelFormat)); - else if (!useDitherer && originalHasAlpha && !pixelFormat.HasAlpha()) + else if (changePixelFormat && !useDitherer && originalHasAlpha && !pixelFormat.HasAlpha()) result.AddInfo(nameof(QuantizerSelectorViewModel.Quantizer), Res.InfoMessageAlphaTurnsBlack); } else if (bppHint > originalBpp) From 5d90f5dac1665bedcc469b520c1733b30f0d57c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 26 May 2021 18:45:56 +0200 Subject: [PATCH 055/211] Adjusting default option for confirmations --- KGySoft.Drawing.ImagingTools/ViewModel/DefaultViewModel.cs | 2 +- .../ViewModel/ImageVisualizerViewModel.cs | 2 +- .../ViewModel/ManageInstallationsViewModel.cs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DefaultViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DefaultViewModel.cs index a676f40..b5475d0 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DefaultViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DefaultViewModel.cs @@ -58,7 +58,7 @@ internal override void ViewLoaded() base.ViewLoaded(); } - internal bool ConfirmIfModified() => !IsModified || Confirm(Res.ConfirmMessageDiscardChanges); + internal bool ConfirmIfModified() => !IsModified || Confirm(Res.ConfirmMessageDiscardChanges, false); #endregion diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs index d6c16b7..41f2fd6 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs @@ -969,7 +969,7 @@ private bool CheckSaveExtension(string fileName) string suggestedExt = filters[((filterIndex - 1) << 1) + 1].ToUpperInvariant(); if (suggestedExt.Split(';').Contains('*' + actualExt)) return true; - return Confirm(Res.ConfirmMessageSaveFileExtension(Path.GetFileName(fileName), filters[(filterIndex - 1) << 1])); + return Confirm(Res.ConfirmMessageSaveFileExtension(Path.GetFileName(fileName), filters[(filterIndex - 1) << 1]), false); } private void SetCurrentImage(Bitmap? image) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs index cd386d1..73d64e1 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs @@ -210,10 +210,10 @@ private void OnSelectFolderCommand() private void OnInstallCommand() { - if (currentStatus.Installed && !Confirm(Res.ConfirmMessageOverwriteInstallation)) + if (currentStatus.Installed && !Confirm(Res.ConfirmMessageOverwriteInstallation, InstallationManager.AvailableVersion.Version > currentStatus.Version)) return; #if NETCOREAPP - if (!Confirm(Res.ConfirmMessageNetCoreVersion)) + if (!Confirm(Res.ConfirmMessageNetCoreVersion, false)) return; #endif @@ -227,7 +227,7 @@ private void OnInstallCommand() private void OnRemoveCommand() { - if (!Confirm(Res.ConfirmMessageRemoveInstallation)) + if (!Confirm(Res.ConfirmMessageRemoveInstallation, false)) return; InstallationManager.Uninstall(currentStatus.Path, out string? error); if (error != null) From a7e8f8364005edf960e877da04e62d0d8cd632ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 26 May 2021 20:17:39 +0200 Subject: [PATCH 056/211] Fixing updating remove command state --- .../ViewModel/ManageInstallationsViewModel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs index 73d64e1..31b3a5e 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs @@ -188,6 +188,7 @@ private void UpdateStatus(string path) StatusText = currentStatus.TargetFramework != null ? Res.InstallationsStatusInstalledWithTargetFramework(currentStatus.Version, currentStatus.TargetFramework) : currentStatus.RuntimeVersion != null ? Res.InstallationsStatusInstalledWithRuntime(currentStatus.Version, currentStatus.RuntimeVersion) : Res.InstallationsStatusInstalled(currentStatus.Version); + RemoveCommandState.Enabled = currentStatus.Installed; } private void SelectFolder() From 4e240d7e48fd23b1f6f1d5d7eddc710fde7460a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 26 May 2021 20:32:29 +0200 Subject: [PATCH 057/211] Resize: less annoying binding format for invalid values --- KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs | 5 ++--- changelog.txt | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs index 590ec33..2169e03 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs @@ -18,7 +18,6 @@ using System; using System.Globalization; - using KGySoft.Drawing.ImagingTools.ViewModel; #endregion @@ -63,9 +62,9 @@ private ResizeBitmapForm() : this(null!) #region Static Methods - private static object FormatPercentage(object value) => ((float)value * 100f).ToString("F0", CultureInfo.CurrentCulture); + private static object FormatPercentage(object value) => value is 0f ? String.Empty : ((float)value * 100f).ToString("F0", CultureInfo.CurrentCulture); private static object ParsePercentage(object value) => Single.TryParse((string)value, NumberStyles.Number, CultureInfo.CurrentCulture, out float result) ? result / 100f : 0f; - private static object FormatInteger(object value) => ((int)value).ToString("F0", CultureInfo.CurrentCulture); + private static object FormatInteger(object value) => value is 0 ? String.Empty : ((int)value).ToString("F0", CultureInfo.CurrentCulture); private static object ParseInteger(object value) => Int32.TryParse((string)value, NumberStyles.Integer, CultureInfo.CurrentCulture, out int result) ? result : 0; #endregion diff --git a/changelog.txt b/changelog.txt index a43be76..c22b465 100644 --- a/changelog.txt +++ b/changelog.txt @@ -16,8 +16,13 @@ + Zooming is now possible also with keyboard shortcuts, the Auto Zoom button has now a drop-down part for the additional options. + Supporting localization from .resx files with editing and on-the-fly application. + Note: Right-to-left languages are also supported though with some limitations, especially under Linux/Mono. + On Windows some validation tool tips are not aligned right but otherwise the layout is arranged correctly. * Manual zooming is enabled even if Auto Zoom is on; this turns off Auto Zoom automatically. * Turning smoothing of zoomed images on/off affects also shrunk images (previously affected enlarged images only). +- Resize: Preventing that invalid sizes replace current text to 0 +- Manage Installations: + - Remove was always enabled even it there was no installed version - Fixing possible errors when closing forms while an async operation is still in progress. * API changes: * Members are annotated for using C# 8.0 nullable references From c8829f44e78268b1cf6a0c725fc98974616d7b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 27 May 2021 11:30:17 +0200 Subject: [PATCH 058/211] Resize form: making binding more convenient, fixing error provider for Linux --- .../View/Forms/ResizeBitmapForm.Designer.cs | 16 +-- .../View/Forms/ResizeBitmapForm.cs | 42 ++++-- .../View/Forms/ResizeBitmapForm.resx | 120 ++++++++++++++++++ .../ViewModel/ResizeBitmapViewModel.cs | 14 +- changelog.txt | 4 +- 5 files changed, 172 insertions(+), 24 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.resx diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs index 4d4ea7b..2379d45 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs @@ -76,7 +76,7 @@ private void InitializeComponent() this.tblNewSize.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); this.tblNewSize.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); this.tblNewSize.Size = new System.Drawing.Size(304, 137); - this.tblNewSize.TabIndex = 1; + this.tblNewSize.TabIndex = 0; // // chbMaintainAspectRatio // @@ -87,7 +87,7 @@ private void InitializeComponent() this.chbMaintainAspectRatio.Location = new System.Drawing.Point(103, 3); this.chbMaintainAspectRatio.Name = "chbMaintainAspectRatio"; this.chbMaintainAspectRatio.Size = new System.Drawing.Size(198, 18); - this.chbMaintainAspectRatio.TabIndex = 10; + this.chbMaintainAspectRatio.TabIndex = 0; this.chbMaintainAspectRatio.Text = "chbMaintainAspectRatio"; this.chbMaintainAspectRatio.UseVisualStyleBackColor = true; // @@ -98,7 +98,7 @@ private void InitializeComponent() this.lblScalingMode.Location = new System.Drawing.Point(3, 108); this.lblScalingMode.Name = "lblScalingMode"; this.lblScalingMode.Size = new System.Drawing.Size(94, 29); - this.lblScalingMode.TabIndex = 8; + this.lblScalingMode.TabIndex = 5; this.lblScalingMode.Text = "lblScalingMode"; this.lblScalingMode.TextAlign = System.Drawing.ContentAlignment.MiddleRight; // @@ -203,7 +203,7 @@ private void InitializeComponent() this.rbByPixels.Location = new System.Drawing.Point(205, 30); this.rbByPixels.Name = "rbByPixels"; this.rbByPixels.Size = new System.Drawing.Size(96, 18); - this.rbByPixels.TabIndex = 1; + this.rbByPixels.TabIndex = 2; this.rbByPixels.TabStop = true; this.rbByPixels.Text = "rbByPixels"; this.rbByPixels.UseVisualStyleBackColor = true; @@ -216,7 +216,7 @@ private void InitializeComponent() this.rbByPercentage.Location = new System.Drawing.Point(103, 30); this.rbByPercentage.Name = "rbByPercentage"; this.rbByPercentage.Size = new System.Drawing.Size(96, 18); - this.rbByPercentage.TabIndex = 0; + this.rbByPercentage.TabIndex = 1; this.rbByPercentage.TabStop = true; this.rbByPercentage.Text = "rbByPercentage"; this.rbByPercentage.UseVisualStyleBackColor = true; @@ -259,7 +259,7 @@ private void InitializeComponent() this.lblWidth.Location = new System.Drawing.Point(3, 54); this.lblWidth.Name = "lblWidth"; this.lblWidth.Size = new System.Drawing.Size(94, 27); - this.lblWidth.TabIndex = 2; + this.lblWidth.TabIndex = 3; this.lblWidth.Text = "lblWidth"; this.lblWidth.TextAlign = System.Drawing.ContentAlignment.MiddleRight; // @@ -270,7 +270,7 @@ private void InitializeComponent() this.lblHeight.Location = new System.Drawing.Point(3, 81); this.lblHeight.Name = "lblHeight"; this.lblHeight.Size = new System.Drawing.Size(94, 27); - this.lblHeight.TabIndex = 5; + this.lblHeight.TabIndex = 4; this.lblHeight.Text = "lblHeight"; this.lblHeight.TextAlign = System.Drawing.ContentAlignment.MiddleRight; // @@ -284,7 +284,7 @@ private void InitializeComponent() this.cmbScalingMode.Location = new System.Drawing.Point(103, 111); this.cmbScalingMode.Name = "cmbScalingMode"; this.cmbScalingMode.Size = new System.Drawing.Size(198, 21); - this.cmbScalingMode.TabIndex = 9; + this.cmbScalingMode.TabIndex = 6; // // ResizeBitmapForm // diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs index 2169e03..17306e7 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs @@ -18,6 +18,8 @@ using System; using System.Globalization; +using System.Windows.Forms; + using KGySoft.Drawing.ImagingTools.ViewModel; #endregion @@ -40,9 +42,8 @@ internal ResizeBitmapForm(ResizeBitmapViewModel viewModel) : base(viewModel) { InitializeComponent(); - - ValidationMapping[nameof(viewModel.Width)] = lblWidth; - ValidationMapping[nameof(viewModel.Height)] = lblHeight; + ValidationMapping[nameof(viewModel.Width)] = lblWidthPercent; + ValidationMapping[nameof(viewModel.Height)] = lblHeightPercent; } #endregion @@ -118,21 +119,40 @@ private void InitPropertyBindings() CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.ByPixels), rbByPixels, nameof(rbByPixels.Checked)); CommandBindings.AddPropertyBinding(ViewModel, nameof(VM.ByPixels), nameof(Enabled), txtWidthPx, txtHeightPx); + // Regular WinForms binding behaves a bit better because it does not clear the currently edited text box on parse error + // but it fails to sync the other properties properly on Linux/Mono so using KGy SOFT binding on non-Windows systems. + // VM.WidthRatio <-> txtWidthPercent.Text - CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.WidthRatio), txtWidthPercent, nameof(txtWidthPercent.Text), - FormatPercentage!, ParsePercentage!); + if (OSUtils.IsWindows) + AddWinFormsBinding(nameof(VM.WidthRatio), txtWidthPercent, nameof(txtWidthPercent.Text), FormatPercentage, ParsePercentage); + else + CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.WidthRatio), txtWidthPercent, nameof(txtWidthPercent.Text), FormatPercentage!, ParsePercentage!); // VM.HeightRatio <-> txtHeightPercent.Text - CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.HeightRatio), txtHeightPercent, nameof(txtHeightPercent.Text), - FormatPercentage!, ParsePercentage!); + if (OSUtils.IsWindows) + AddWinFormsBinding(nameof(VM.HeightRatio), txtHeightPercent, nameof(txtHeightPercent.Text), FormatPercentage, ParsePercentage); + else + CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.HeightRatio), txtHeightPercent, nameof(txtHeightPercent.Text), FormatPercentage!, ParsePercentage!); // VM.Width <-> txtWidthPx.Text - CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.Width), txtWidthPx, nameof(txtWidthPx.Text), - FormatInteger!, ParseInteger!); + if (OSUtils.IsWindows) + AddWinFormsBinding(nameof(VM.Width), txtWidthPx, nameof(txtWidthPx.Text), FormatInteger, ParseInteger); + else + CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.Width), txtWidthPx, nameof(txtWidthPx.Text), FormatInteger!, ParseInteger!); // VM.Height <-> txtHeightPx.Text - CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.Height), txtHeightPx, nameof(txtHeightPx.Text), - FormatInteger!, ParseInteger!); + if (OSUtils.IsWindows) + AddWinFormsBinding(nameof(VM.Height), txtHeightPx, nameof(txtHeightPx.Text), FormatInteger, ParseInteger); + else + CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.Height), txtHeightPx, nameof(txtHeightPx.Text), FormatInteger!, ParseInteger!); + } + + private void AddWinFormsBinding(string sourceName, IBindableComponent target, string propertyName, Func format, Func parse) + { + var binding = new Binding(propertyName, ViewModel, sourceName, true, DataSourceUpdateMode.OnPropertyChanged); + binding.Format += (_, e) => e.Value = format.Invoke(e.Value); + binding.Parse += (_, e) => e.Value = parse.Invoke(e.Value); + target.DataBindings.Add(binding); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.resx b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ResizeBitmapViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ResizeBitmapViewModel.cs index bf2b02e..ea54cd5 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ResizeBitmapViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ResizeBitmapViewModel.cs @@ -168,15 +168,21 @@ protected override void Dispose(bool disposing) #region Properties + #region Public Properties + + // The binding needs these to be public + public int Width { get => Get(originalSize.Width); set => Set(value); } + public int Height { get => Get(originalSize.Height); set => Set(value); } + public float WidthRatio { get => Get(1f); set => Set(value); } + public float HeightRatio { get => Get(1f); set => Set(value); } + + #endregion + #region Internal Properties internal bool KeepAspectRatio { get => Get(true); set => Set(value); } internal bool ByPercentage { get => Get(true); set => Set(value); } internal bool ByPixels { get => Get(false); set => Set(value); } - internal int Width { get => Get(originalSize.Width); set => Set(value); } - internal int Height { get => Get(originalSize.Height); set => Set(value); } - internal float WidthRatio { get => Get(1f); set => Set(value); } - internal float HeightRatio { get => Get(1f); set => Set(value); } internal ScalingMode[] ScalingModes => Get(Enum.GetValues); internal ScalingMode ScalingMode { get => Get(); set => Set(value); } diff --git a/changelog.txt b/changelog.txt index c22b465..e83a49c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -20,7 +20,9 @@ On Windows some validation tool tips are not aligned right but otherwise the layout is arranged correctly. * Manual zooming is enabled even if Auto Zoom is on; this turns off Auto Zoom automatically. * Turning smoothing of zoomed images on/off affects also shrunk images (previously affected enlarged images only). -- Resize: Preventing that invalid sizes replace current text to 0 +- Resize: + - Preventing that invalid sizes replace current text to 0 + - Changing error provider associated controls so the layout is not messed up on Linux - Manage Installations: - Remove was always enabled even it there was no installed version - Fixing possible errors when closing forms while an async operation is still in progress. From 77e19589bb8a3521d171eba08770b9195a4e7089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 27 May 2021 14:21:16 +0200 Subject: [PATCH 059/211] Fixing image preview tool strip appearance on Linux --- .../View/UserControls/PreviewImageControl.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs index a39e24d..564fee9 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/PreviewImageControl.cs @@ -16,6 +16,7 @@ #region Usings +using System; using System.Drawing; using System.Windows.Forms; @@ -77,6 +78,12 @@ public PreviewImageControl() #region Protected Methods + protected override void OnLoad(EventArgs e) + { + tsMenu.FixAppearance(); + base.OnLoad(e); + } + protected override void Dispose(bool disposing) { if (disposing) From 1f8834f2cbf80489b1d1cab65210aa37849af0a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 27 May 2021 15:23:35 +0200 Subject: [PATCH 060/211] ImageViewer: balancing out performance --- .../View/Controls/ImageViewer.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 85e91f2..dad38fa 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -132,7 +132,7 @@ internal PreviewGenerator(ImageViewer owner) try { // Converting non supported or too slow pixel formats - if (pixelFormat.In(convertedFormats) || pixelFormat != PixelFormat.Format32bppPArgb && (image.Width > generateThreshold || image.Height > generateThreshold)) + if (pixelFormat.In(convertedFormats) || pixelFormat != PixelFormat.Format32bppPArgb && (image.Width > sizeThreshold || image.Height > sizeThreshold)) { safeDefaultImage = image.ConvertPixelFormat(PixelFormat.Format32bppPArgb); isClonedSafeDefaultImage = true; @@ -199,7 +199,7 @@ internal void BeginGenerateDisplayImage() Size size = owner.targetRectangle.Size; bool isGenerateNeeded = owner.isMetafile ? owner.smoothZooming - : owner.smoothZooming && owner.zoom < 1f && (owner.imageSize.Width >= generateThreshold || owner.imageSize.Height >= generateThreshold); + : owner.smoothZooming && owner.zoom < 1f && (owner.imageSize.Width > sizeThreshold || owner.imageSize.Height > sizeThreshold); if (!isGenerateNeeded || size.Width < 1 || size.Height < 1) { @@ -515,7 +515,7 @@ private void DoGenerate(object state) #region Constants - private const int generateThreshold = 1000; + private const int sizeThreshold = 1024; #endregion @@ -991,7 +991,16 @@ private void PaintImage(Graphics g) dest.X -= sbHorizontal.Value; if (sbVerticalVisible) dest.Y -= sbVertical.Value; - g.InterpolationMode = !isMetafile && (smoothZooming && zoom > 1f || smoothZooming && zoom < 1f && imageSize.Width < generateThreshold && imageSize.Height < generateThreshold) ? InterpolationMode.HighQualityBicubic : InterpolationMode.NearestNeighbor; + + // metafile or smoothing is off (smoothed metafile is generated async so it replaces the aliased result after some delay): NN + g.InterpolationMode = isMetafile || !smoothZooming ? InterpolationMode.NearestNeighbor + // large zoom or small shrunk image: BC because these cases it's not so slow + : zoom >= 4f || zoom < 1f && imageSize.Width <= sizeThreshold && imageSize.Height <= sizeThreshold ? InterpolationMode.HighQualityBicubic + // small zoom: BL for large images to prevent heavy lagging; otherwise, BC + : zoom > 1f ? imageSize.Width > sizeThreshold || imageSize.Height > sizeThreshold ? InterpolationMode.HighQualityBilinear : InterpolationMode.HighQualityBicubic + // anything else, including large shrunk images: NN (the good quality preview is generated async so it replaces the NN result after some delay) + : InterpolationMode.NearestNeighbor; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; // This lock ensures that no disposed image is painted. The generator also locks on itself when frees the cached preview. From bf342bdfca4d34152f5246d71bffcfd5a3fab679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 27 May 2021 16:54:10 +0200 Subject: [PATCH 061/211] Palette visualizer: adding ok/cancel --- .../View/Controls/PalettePanel.cs | 2 +- .../View/Forms/PaletteVisualizerForm.Designer.cs | 15 +++++++++++++-- .../View/Forms/PaletteVisualizerForm.cs | 6 ++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs index 2a6826d..cea9347 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs @@ -158,7 +158,7 @@ internal Color SelectedColor #region Constructors - internal PalettePanel() + public PalettePanel() { InitializeComponent(); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.Designer.cs index 2b780db..57799b1 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.Designer.cs @@ -21,7 +21,8 @@ private void InitializeComponent() this.gbPalette = new System.Windows.Forms.GroupBox(); this.pnlPalette = new PalettePanel(); this.gbSelectedColor = new System.Windows.Forms.GroupBox(); - this.ucColorVisualizer = new ColorVisualizerControl(); + this.ucColorVisualizer = new KGySoft.Drawing.ImagingTools.View.UserControls.ColorVisualizerControl(); + this.okCancelButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); this.gbPalette.SuspendLayout(); this.gbSelectedColor.SuspendLayout(); this.SuspendLayout(); @@ -63,13 +64,22 @@ private void InitializeComponent() this.ucColorVisualizer.Size = new System.Drawing.Size(241, 197); this.ucColorVisualizer.TabIndex = 0; // + // okCancelButtons + // + this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; + this.okCancelButtons.Location = new System.Drawing.Point(0, 450); + this.okCancelButtons.Name = "okCancelButtons"; + this.okCancelButtons.Size = new System.Drawing.Size(247, 40); + this.okCancelButtons.TabIndex = 2; + // // PaletteVisualizerForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(247, 450); + this.ClientSize = new System.Drawing.Size(247, 490); this.Controls.Add(this.gbPalette); this.Controls.Add(this.gbSelectedColor); + this.Controls.Add(this.okCancelButtons); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; this.MaximumSize = new System.Drawing.Size(280, 32867); this.MinimumSize = new System.Drawing.Size(255, 300); @@ -86,5 +96,6 @@ private void InitializeComponent() private PalettePanel pnlPalette; private System.Windows.Forms.GroupBox gbSelectedColor; private ColorVisualizerControl ucColorVisualizer; + private OkCancelButtons okCancelButtons; } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs index 0cb5add..c5f722f 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs @@ -32,6 +32,8 @@ internal PaletteVisualizerForm(PaletteVisualizerViewModel viewModel) : base(viewModel) { InitializeComponent(); + AcceptButton = okCancelButtons.OKButton; + CancelButton = okCancelButtons.CancelButton; } #endregion @@ -97,6 +99,8 @@ private void InitCommandBindings() .AddSource(ucColorVisualizer, nameof(ucColorVisualizer.ColorEdited)); CommandBindings.Add(OnSelectedColorChangedCommand) .AddSource(pnlPalette, nameof(pnlPalette.SelectedColorChanged)); + CommandBindings.Add(OnCancelCommand) + .AddSource(okCancelButtons.CancelButton, nameof(okCancelButtons.CancelButton.Click)); } private void UpdateInfo() => ucColorVisualizer.SpecialInfo = Res.InfoSelectedIndex(pnlPalette.SelectedColorIndex); @@ -118,6 +122,8 @@ private void OnColorEditedCommand() private void OnSelectedColorChangedCommand() => UpdateInfo(); + private void OnCancelCommand() => ViewModel.SetModified(false); + #endregion #endregion From 76c35d9e8a346acbdde462520cb1355365ed08be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 27 May 2021 17:11:03 +0200 Subject: [PATCH 062/211] Adjusting ok/cancel panel height --- .../View/Forms/EditResourcesForm.Designer.cs | 2 +- .../Forms/LanguageSettingsForm.Designer.cs | 2 +- .../Forms/PaletteVisualizerForm.Designer.cs | 4 +-- .../Forms/TransformBitmapFormBase.Designer.cs | 2 +- .../DithererStrengthEditorControl.Designer.cs | 2 +- .../UserControls/OkCancelButtons.Designer.cs | 30 +++++++++---------- ...uantizerThresholdEditorControl.Designer.cs | 2 +- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index 446625d..5e02816 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -219,7 +219,7 @@ private void InitializeComponent() this.okCancelApplyButtons.Dock = System.Windows.Forms.DockStyle.Bottom; this.okCancelApplyButtons.Location = new System.Drawing.Point(3, 268); this.okCancelApplyButtons.Name = "okCancelApplyButtons"; - this.okCancelApplyButtons.Size = new System.Drawing.Size(578, 40); + this.okCancelApplyButtons.Size = new System.Drawing.Size(578, 35); this.okCancelApplyButtons.TabIndex = 1; // // EditResourcesForm diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs index 4b8f6ca..95c75a6 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs @@ -50,7 +50,7 @@ private void InitializeComponent() this.okCancelApplyButtons.Dock = System.Windows.Forms.DockStyle.Bottom; this.okCancelApplyButtons.Location = new System.Drawing.Point(3, 108); this.okCancelApplyButtons.Name = "okCancelApplyButtons"; - this.okCancelApplyButtons.Size = new System.Drawing.Size(298, 40); + this.okCancelApplyButtons.Size = new System.Drawing.Size(298, 35); this.okCancelApplyButtons.TabIndex = 1; // // gbAllowResxResources diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.Designer.cs index 57799b1..0a8d7a1 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.Designer.cs @@ -69,7 +69,7 @@ private void InitializeComponent() this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; this.okCancelButtons.Location = new System.Drawing.Point(0, 450); this.okCancelButtons.Name = "okCancelButtons"; - this.okCancelButtons.Size = new System.Drawing.Size(247, 40); + this.okCancelButtons.Size = new System.Drawing.Size(247, 35); this.okCancelButtons.TabIndex = 2; // // PaletteVisualizerForm @@ -82,7 +82,7 @@ private void InitializeComponent() this.Controls.Add(this.okCancelButtons); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; this.MaximumSize = new System.Drawing.Size(280, 32867); - this.MinimumSize = new System.Drawing.Size(255, 300); + this.MinimumSize = new System.Drawing.Size(255, 335); this.Name = "PaletteVisualizerForm"; this.gbPalette.ResumeLayout(false); this.gbSelectedColor.ResumeLayout(false); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs index cbc23d5..52bc769 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs @@ -43,7 +43,7 @@ private void InitializeComponent() this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; this.okCancelButtons.Location = new System.Drawing.Point(0, 149); this.okCancelButtons.Name = "okCancelButtons"; - this.okCancelButtons.Size = new System.Drawing.Size(248, 40); + this.okCancelButtons.Size = new System.Drawing.Size(248, 35); this.okCancelButtons.TabIndex = 2; // // previewImage diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.Designer.cs index 9666150..6f718df 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.Designer.cs @@ -37,7 +37,7 @@ private void InitializeComponent() this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; this.okCancelButtons.Location = new System.Drawing.Point(0, 27); this.okCancelButtons.Name = "okCancelButtons"; - this.okCancelButtons.Size = new System.Drawing.Size(182, 40); + this.okCancelButtons.Size = new System.Drawing.Size(182, 35); this.okCancelButtons.TabIndex = 1; // // lblValue diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs index be00b01..ea71a13 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs @@ -16,9 +16,9 @@ partial class OkCancelButtons private void InitializeComponent() { this.pnlButtons = new System.Windows.Forms.FlowLayoutPanel(); + this.btnApply = new System.Windows.Forms.Button(); this.btnCancel = new System.Windows.Forms.Button(); this.btnOK = new System.Windows.Forms.Button(); - this.btnApply = new System.Windows.Forms.Button(); this.pnlButtons.SuspendLayout(); this.SuspendLayout(); // @@ -32,9 +32,21 @@ private void InitializeComponent() this.pnlButtons.Location = new System.Drawing.Point(0, 0); this.pnlButtons.Name = "pnlButtons"; this.pnlButtons.Padding = new System.Windows.Forms.Padding(3); - this.pnlButtons.Size = new System.Drawing.Size(260, 40); + this.pnlButtons.Size = new System.Drawing.Size(260, 35); this.pnlButtons.TabIndex = 0; // + // btnApply + // + this.btnApply.Anchor = System.Windows.Forms.AnchorStyles.None; + this.btnApply.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.btnApply.Location = new System.Drawing.Point(176, 6); + this.btnApply.Name = "btnApply"; + this.btnApply.Size = new System.Drawing.Size(75, 23); + this.btnApply.TabIndex = 2; + this.btnApply.Text = "btnApply"; + this.btnApply.UseVisualStyleBackColor = true; + this.btnApply.Visible = false; + // // btnCancel // this.btnCancel.Anchor = System.Windows.Forms.AnchorStyles.None; @@ -59,25 +71,13 @@ private void InitializeComponent() this.btnOK.Text = "btnOK"; this.btnOK.UseVisualStyleBackColor = true; // - // btnApply - // - this.btnApply.Anchor = System.Windows.Forms.AnchorStyles.None; - this.btnApply.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.btnApply.Location = new System.Drawing.Point(176, 6); - this.btnApply.Name = "btnApply"; - this.btnApply.Size = new System.Drawing.Size(75, 23); - this.btnApply.TabIndex = 2; - this.btnApply.Text = "btnApply"; - this.btnApply.UseVisualStyleBackColor = true; - this.btnApply.Visible = false; - // // OkCancelButtons // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Controls.Add(this.pnlButtons); this.Name = "OkCancelButtons"; - this.Size = new System.Drawing.Size(260, 40); + this.Size = new System.Drawing.Size(260, 35); this.pnlButtons.ResumeLayout(false); this.ResumeLayout(false); diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.Designer.cs index 109dd6e..75588c6 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.Designer.cs @@ -38,7 +38,7 @@ private void InitializeComponent() this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; this.okCancelButtons.Location = new System.Drawing.Point(0, 27); this.okCancelButtons.Name = "okCancelButtons"; - this.okCancelButtons.Size = new System.Drawing.Size(182, 40); + this.okCancelButtons.Size = new System.Drawing.Size(182, 35); this.okCancelButtons.TabIndex = 1; // // lblValue From 2147db29f8e9462e804a3c37e1492b0f09e72866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 27 May 2021 17:14:47 +0200 Subject: [PATCH 063/211] Color visualizer: adding ok/cancel buttons --- .../View/Forms/ColorVisualizerForm.Designer.cs | 17 ++++++++++++++--- .../View/Forms/ColorVisualizerForm.cs | 6 ++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.Designer.cs index 072b8c8..c067a02 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.Designer.cs @@ -18,7 +18,8 @@ partial class ColorVisualizerForm /// private void InitializeComponent() { - this.ucColorVisualizer = new ColorVisualizerControl(); + this.ucColorVisualizer = new KGySoft.Drawing.ImagingTools.View.UserControls.ColorVisualizerControl(); + this.okCancelButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); this.SuspendLayout(); // // ucColorVisualizer @@ -26,15 +27,24 @@ private void InitializeComponent() this.ucColorVisualizer.Dock = System.Windows.Forms.DockStyle.Fill; this.ucColorVisualizer.Location = new System.Drawing.Point(0, 0); this.ucColorVisualizer.Name = "ucColorVisualizer"; - this.ucColorVisualizer.Size = new System.Drawing.Size(244, 200); + this.ucColorVisualizer.Size = new System.Drawing.Size(244, 201); this.ucColorVisualizer.TabIndex = 1; // + // okCancelButtons1 + // + this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; + this.okCancelButtons.Location = new System.Drawing.Point(0, 201); + this.okCancelButtons.Name = "okCancelButtons"; + this.okCancelButtons.Size = new System.Drawing.Size(244, 35); + this.okCancelButtons.TabIndex = 2; + // // ColorVisualizerForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(244, 200); + this.ClientSize = new System.Drawing.Size(244, 241); this.Controls.Add(this.ucColorVisualizer); + this.Controls.Add(this.okCancelButtons); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; this.MinimumSize = new System.Drawing.Size(260, 234); this.Name = "ColorVisualizerForm"; @@ -45,5 +55,6 @@ private void InitializeComponent() #endregion private ColorVisualizerControl ucColorVisualizer; + private OkCancelButtons okCancelButtons; } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs index ae2f3f7..88ee964 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs @@ -33,6 +33,8 @@ internal ColorVisualizerForm(ColorVisualizerViewModel viewModel) : base(viewModel) { InitializeComponent(); + AcceptButton = okCancelButtons.OKButton; + CancelButton = okCancelButtons.CancelButton; } #endregion @@ -91,6 +93,8 @@ private void InitCommandBindings() { CommandBindings.Add(OnColorEditedCommand) .AddSource(ucColorVisualizer, nameof(ucColorVisualizer.ColorEdited)); + CommandBindings.Add(OnCancelCommand) + .AddSource(okCancelButtons.CancelButton, nameof(okCancelButtons.CancelButton.Click)); } #endregion @@ -104,6 +108,8 @@ private void OnColorEditedCommand() ViewModel.Color = ucColorVisualizer.Color; } + private void OnCancelCommand() => ViewModel.SetModified(false); + #endregion #endregion From 269e22949f028046f6518c3d5cc1f577f8f835e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 27 May 2021 17:48:09 +0200 Subject: [PATCH 064/211] Palette/Color visualizer forms: OK is enabled only after modification --- .../View/Forms/ColorVisualizerForm.cs | 12 ++++++++++++ .../View/Forms/PaletteVisualizerForm.cs | 12 ++++++++++++ changelog.txt | 2 ++ 3 files changed, 26 insertions(+) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs index 88ee964..cf8a900 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs @@ -17,6 +17,8 @@ #region Usings using System.Drawing; +using System.Windows.Forms; + using KGySoft.Drawing.ImagingTools.ViewModel; #endregion @@ -67,6 +69,13 @@ protected override void ApplyViewModel() base.ApplyViewModel(); } + protected override void OnFormClosing(FormClosingEventArgs e) + { + if (e.CloseReason == CloseReason.UserClosing || DialogResult == DialogResult.Cancel) + ViewModel.SetModified(false); + base.OnFormClosing(e); + } + protected override void Dispose(bool disposing) { if (disposing) @@ -87,6 +96,9 @@ private void InitPropertyBindings() // VM.Color -> ucColorVisualizer.Color, Text CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Color), nameof(ucColorVisualizer.Color), ucColorVisualizer); CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Color), nameof(Text), c => Res.TitleColor((Color)c!), this); + + // VM.IsModified -> OKButton.Enabled + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.IsModified), nameof(okCancelButtons.OKButton.Enabled), okCancelButtons.OKButton); } private void InitCommandBindings() diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs index c5f722f..b73abcc 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs @@ -16,6 +16,8 @@ #region Usings +using System.Windows.Forms; + using KGySoft.Drawing.ImagingTools.ViewModel; #endregion @@ -67,6 +69,13 @@ protected override void ApplyViewModel() base.ApplyViewModel(); } + protected override void OnFormClosing(FormClosingEventArgs e) + { + if (e.CloseReason == CloseReason.UserClosing || DialogResult == DialogResult.Cancel) + ViewModel.SetModified(false); + base.OnFormClosing(e); + } + protected override void Dispose(bool disposing) { if (disposing) @@ -91,6 +100,9 @@ private void InitPropertyBindings() // pnlPalette.SelectedColor -> ucColorVisualizer.Color CommandBindings.AddPropertyBinding(pnlPalette, nameof(pnlPalette.SelectedColor), nameof(ucColorVisualizer.Color), ucColorVisualizer); + + // VM.IsModified -> OKButton.Enabled + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.IsModified), nameof(okCancelButtons.OKButton.Enabled), okCancelButtons.OKButton); } private void InitCommandBindings() diff --git a/changelog.txt b/changelog.txt index e83a49c..d291f48 100644 --- a/changelog.txt +++ b/changelog.txt @@ -18,6 +18,8 @@ + Supporting localization from .resx files with editing and on-the-fly application. Note: Right-to-left languages are also supported though with some limitations, especially under Linux/Mono. On Windows some validation tool tips are not aligned right but otherwise the layout is arranged correctly. ++ Palette visualizer form: OK/Cancel buttons ++ Color visualizer form: OK/Cancel buttons * Manual zooming is enabled even if Auto Zoom is on; this turns off Auto Zoom automatically. * Turning smoothing of zoomed images on/off affects also shrunk images (previously affected enlarged images only). - Resize: From c51005c921dec73b68bbb25abcbe1d4397f934cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 27 May 2021 18:20:05 +0200 Subject: [PATCH 065/211] Refactoring: using LanguageSettings also for formatting language --- .../View/Forms/AdjustColorsFormBase.cs | 4 +--- .../View/Forms/ColorVisualizerForm.Designer.cs | 2 +- .../View/Forms/ResizeBitmapForm.cs | 8 ++++---- .../View/UserControls/DithererStrengthEditorControl.cs | 3 +-- .../View/UserControls/QuantizerThresholdEditorControl.cs | 3 +-- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.cs index d6b08a9..0e7f5a8 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.cs @@ -16,8 +16,6 @@ #region Usings -using System.Globalization; - using KGySoft.CoreLibraries; using KGySoft.Drawing.ImagingTools.ViewModel; @@ -120,7 +118,7 @@ private void InitPropertyBindings() value => (int)value! / 100f); // VM.Value -> lblValue.Text - CommandBindings.AddPropertyBinding(ViewModel, nameof(VM.Value), nameof(lblValue.Text), v => ((float)v!).ToString("F2", CultureInfo.CurrentCulture), lblValue); + CommandBindings.AddPropertyBinding(ViewModel, nameof(VM.Value), nameof(lblValue.Text), v => ((float)v!).ToString("F2", LanguageSettings.FormattingLanguage), lblValue); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.Designer.cs index c067a02..c5e2355 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.Designer.cs @@ -30,7 +30,7 @@ private void InitializeComponent() this.ucColorVisualizer.Size = new System.Drawing.Size(244, 201); this.ucColorVisualizer.TabIndex = 1; // - // okCancelButtons1 + // okCancelButtons // this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; this.okCancelButtons.Location = new System.Drawing.Point(0, 201); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs index 17306e7..5d152e4 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs @@ -63,10 +63,10 @@ private ResizeBitmapForm() : this(null!) #region Static Methods - private static object FormatPercentage(object value) => value is 0f ? String.Empty : ((float)value * 100f).ToString("F0", CultureInfo.CurrentCulture); - private static object ParsePercentage(object value) => Single.TryParse((string)value, NumberStyles.Number, CultureInfo.CurrentCulture, out float result) ? result / 100f : 0f; - private static object FormatInteger(object value) => value is 0 ? String.Empty : ((int)value).ToString("F0", CultureInfo.CurrentCulture); - private static object ParseInteger(object value) => Int32.TryParse((string)value, NumberStyles.Integer, CultureInfo.CurrentCulture, out int result) ? result : 0; + private static object FormatPercentage(object value) => value is 0f ? String.Empty : ((float)value * 100f).ToString("F0", LanguageSettings.FormattingLanguage); + private static object ParsePercentage(object value) => Single.TryParse((string)value, NumberStyles.Number, LanguageSettings.FormattingLanguage, out float result) ? result / 100f : 0f; + private static object FormatInteger(object value) => value is 0 ? String.Empty : ((int)value).ToString("F0", LanguageSettings.FormattingLanguage); + private static object ParseInteger(object value) => Int32.TryParse((string)value, NumberStyles.Integer, LanguageSettings.FormattingLanguage, out int result) ? result : 0; #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs index dc95106..d7a716e 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs @@ -17,7 +17,6 @@ #region Usings using System; -using System.Globalization; using System.Windows.Forms; using System.Windows.Forms.Design; @@ -99,7 +98,7 @@ protected override bool ProcessDialogKey(Keys keyData) #region Private Methods - private void UpdateLabel() => lblValue.Text = Value <= 0f ? Res.TextAuto : Value.ToString("F2", CultureInfo.CurrentCulture); + private void UpdateLabel() => lblValue.Text = Value <= 0f ? Res.TextAuto : Value.ToString("F2", LanguageSettings.FormattingLanguage); #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs index cf741b5..b07b4c1 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs @@ -17,7 +17,6 @@ #region Usings using System; -using System.Globalization; using System.Windows.Forms; using System.Windows.Forms.Design; @@ -100,7 +99,7 @@ protected override bool ProcessDialogKey(Keys keyData) private void TrackBar_ValueChanged(object? sender, EventArgs e) { Value = (byte)trackBar.Value; - lblValue.Text = Value.ToString(CultureInfo.CurrentCulture); + lblValue.Text = Value.ToString(LanguageSettings.FormattingLanguage); } private void OKButton_Click(object? sender, EventArgs e) From fc6bdfc101331a8cdafa061dc3c7a0fa92d309be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 27 May 2021 18:28:27 +0200 Subject: [PATCH 066/211] ZoomSplitButton: preventing generating an image in the designer --- .../View/Components/ZoomSplitButton.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs index 53f0c61..6b49825 100644 --- a/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs @@ -17,6 +17,8 @@ #region Usings using System; +using System.ComponentModel; +using System.Drawing; using System.Windows.Forms; #endregion @@ -27,6 +29,16 @@ internal class ZoomSplitButton : AdvancedToolStripSplitButton { #region Properties + #region Public Properties + + // Overridden just to prevent saving a fixed low-res image in the .resx file + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override Image Image { get => base.Image; set => base.Image = value; } + + #endregion + + #region Internal Properties + internal ToolStripMenuItem AutoZoomMenuItem { get; } internal ToolStripMenuItem IncreaseZoomMenuItem { get; } internal ToolStripMenuItem DecreaseZoomMenuItem { get; } @@ -34,6 +46,8 @@ internal class ZoomSplitButton : AdvancedToolStripSplitButton #endregion + #endregion + #region Constructors public ZoomSplitButton() From 2150de8311dec768a98214678bc706ea18830cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 27 May 2021 18:49:16 +0200 Subject: [PATCH 067/211] Image visualizer form: OK/Cancel buttons --- .../View/Forms/AppMainForm.cs | 6 +++ .../Forms/ImageVisualizerForm.Designer.cs | 48 ++++++++++++------- .../View/Forms/ImageVisualizerForm.cs | 16 ++++++- changelog.txt | 1 + 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs index 37fe89d..c8ad6d2 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs @@ -65,6 +65,7 @@ internal AppMainForm(DefaultViewModel viewModel) : base(viewModel) { InitializeComponent(); + okCancelButtons.Visible = false; } #endregion @@ -93,7 +94,12 @@ protected override void ApplyViewModel() protected override void OnFormClosing(FormClosingEventArgs e) { if (e.CloseReason == CloseReason.UserClosing && ViewModel.IsModified) + { e.Cancel = !ViewModel.ConfirmIfModified(); + if (e.Cancel) + DialogResult = DialogResult.None; + } + base.OnFormClosing(e); } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs index fe28052..4831e78 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs @@ -28,21 +28,21 @@ private void InitializeComponent() this.lblNotification = new KGySoft.Drawing.ImagingTools.View.Controls.NotificationLabel(); this.splitter = new System.Windows.Forms.Splitter(); this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.AdvancedToolStrip(); - this.btnZoom = new ZoomSplitButton(); + this.btnZoom = new KGySoft.Drawing.ImagingTools.View.Components.ZoomSplitButton(); this.btnAntiAlias = new System.Windows.Forms.ToolStripButton(); this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); this.btnOpen = new System.Windows.Forms.ToolStripButton(); this.btnSave = new System.Windows.Forms.ToolStripButton(); this.btnClear = new System.Windows.Forms.ToolStripButton(); this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); - this.btnColorSettings = new ScalingToolStripDropDownButton(); + this.btnColorSettings = new KGySoft.Drawing.ImagingTools.View.Components.ScalingToolStripDropDownButton(); this.miBackColor = new System.Windows.Forms.ToolStripMenuItem(); this.miBackColorDefault = new System.Windows.Forms.ToolStripMenuItem(); this.miBackColorWhite = new System.Windows.Forms.ToolStripMenuItem(); this.miBackColorBlack = new System.Windows.Forms.ToolStripMenuItem(); this.miShowPalette = new System.Windows.Forms.ToolStripMenuItem(); this.miCountColors = new System.Windows.Forms.ToolStripMenuItem(); - this.btnEdit = new ScalingToolStripDropDownButton(); + this.btnEdit = new KGySoft.Drawing.ImagingTools.View.Components.ScalingToolStripDropDownButton(); this.miRotateLeft = new System.Windows.Forms.ToolStripMenuItem(); this.miRotateRight = new System.Windows.Forms.ToolStripMenuItem(); this.miResizeBitmap = new System.Windows.Forms.ToolStripMenuItem(); @@ -57,10 +57,11 @@ private void InitializeComponent() this.btnPrev = new System.Windows.Forms.ToolStripButton(); this.btnNext = new System.Windows.Forms.ToolStripButton(); this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); - this.btnConfiguration = new AdvancedToolStripSplitButton(); - this.txtInfo = new System.Windows.Forms.TextBox(); + this.btnConfiguration = new KGySoft.Drawing.ImagingTools.View.Components.AdvancedToolStripSplitButton(); this.miManageInstallations = new System.Windows.Forms.ToolStripMenuItem(); this.miLanguageSettings = new System.Windows.Forms.ToolStripMenuItem(); + this.txtInfo = new System.Windows.Forms.TextBox(); + this.okCancelButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); this.tsMenu.SuspendLayout(); this.SuspendLayout(); // @@ -69,7 +70,7 @@ private void InitializeComponent() this.imageViewer.Dock = System.Windows.Forms.DockStyle.Fill; this.imageViewer.Location = new System.Drawing.Point(0, 49); this.imageViewer.Name = "imageViewer"; - this.imageViewer.Size = new System.Drawing.Size(334, 141); + this.imageViewer.Size = new System.Drawing.Size(334, 106); this.imageViewer.TabIndex = 1; this.imageViewer.TabStop = false; // @@ -91,8 +92,9 @@ private void InitializeComponent() // splitter // this.splitter.Dock = System.Windows.Forms.DockStyle.Bottom; - this.splitter.Location = new System.Drawing.Point(0, 190); + this.splitter.Location = new System.Drawing.Point(0, 155); this.splitter.MinExtra = 16; + this.splitter.MinSize = 50; this.splitter.Name = "splitter"; this.splitter.Size = new System.Drawing.Size(334, 3); this.splitter.TabIndex = 3; @@ -353,10 +355,22 @@ private void InitializeComponent() this.btnConfiguration.Name = "btnConfiguration"; this.btnConfiguration.Size = new System.Drawing.Size(16, 22); // + // miManageInstallations + // + this.miManageInstallations.Name = "miManageInstallations"; + this.miManageInstallations.Size = new System.Drawing.Size(194, 22); + this.miManageInstallations.Text = "miManageInstallations"; + // + // miLanguageSettings + // + this.miLanguageSettings.Name = "miLanguageSettings"; + this.miLanguageSettings.Size = new System.Drawing.Size(194, 22); + this.miLanguageSettings.Text = "miLanguageSettings"; + // // txtInfo // this.txtInfo.Dock = System.Windows.Forms.DockStyle.Bottom; - this.txtInfo.Location = new System.Drawing.Point(0, 193); + this.txtInfo.Location = new System.Drawing.Point(0, 158); this.txtInfo.Multiline = true; this.txtInfo.Name = "txtInfo"; this.txtInfo.ReadOnly = true; @@ -366,17 +380,13 @@ private void InitializeComponent() this.txtInfo.TabStop = false; this.txtInfo.WordWrap = false; // - // miManageInstallations - // - this.miManageInstallations.Name = "miManageInstallations"; - this.miManageInstallations.Size = new System.Drawing.Size(194, 22); - this.miManageInstallations.Text = "miManageInstallations"; - // - // miLanguageSettings + // okCancelButtons // - this.miLanguageSettings.Name = "miLanguageSettings"; - this.miLanguageSettings.Size = new System.Drawing.Size(194, 22); - this.miLanguageSettings.Text = "miLanguageSettings"; + this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; + this.okCancelButtons.Location = new System.Drawing.Point(0, 281); + this.okCancelButtons.Name = "okCancelButtons"; + this.okCancelButtons.Size = new System.Drawing.Size(334, 35); + this.okCancelButtons.TabIndex = 5; // // ImageVisualizerForm // @@ -388,6 +398,7 @@ private void InitializeComponent() this.Controls.Add(this.splitter); this.Controls.Add(this.tsMenu); this.Controls.Add(this.txtInfo); + this.Controls.Add(this.okCancelButtons); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; this.MinimumSize = new System.Drawing.Size(200, 200); this.Name = "ImageVisualizerForm"; @@ -440,5 +451,6 @@ private void InitializeComponent() private ToolStripSeparator toolStripSeparator4; private ToolStripMenuItem miManageInstallations; private ToolStripMenuItem miLanguageSettings; + protected UserControls.OkCancelButtons okCancelButtons; } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index b5b953f..5552c38 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -38,6 +38,8 @@ internal ImageVisualizerForm(ImageVisualizerViewModel viewModel) : base(viewModel) { InitializeComponent(); + AcceptButton = okCancelButtons.OKButton; + CancelButton = okCancelButtons.CancelButton; } #endregion @@ -155,6 +157,13 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData) } } + protected override void OnFormClosing(FormClosingEventArgs e) + { + if (DialogResult == DialogResult.Cancel) + ViewModel.SetModified(false); + base.OnFormClosing(e); + } + protected override void Dispose(bool disposing) { if (disposing) @@ -224,7 +233,11 @@ private void InitPropertyBindings() CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.SaveFileFilter), nameof(dlgSave.Filter), dlgSave); CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.SaveFileFilterIndex), nameof(dlgSave.FilterIndex), dlgSave); CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.SaveFileDefaultExtension), nameof(dlgSave.DefaultExt), dlgSave); + + // VM.IsModified -> OKButton.Enabled + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.IsModified), nameof(okCancelButtons.OKButton.Enabled), okCancelButtons.OKButton); } + private void InitCommandBindings() { // View @@ -324,7 +337,8 @@ private void AdjustSize() int minHeight = new Size(16, 16).Scale(this.GetScale()).Height + SystemInformation.HorizontalScrollBarHeight; if (imageViewer.Height >= minHeight) return; - txtInfo.Height = ClientSize.Height - tsMenu.Height - splitter.Height - minHeight; + int buttonsHeight = okCancelButtons.Visible ? okCancelButtons.Height : 0; + txtInfo.Height = ClientSize.Height - tsMenu.Height - splitter.Height - buttonsHeight - minHeight; PerformLayout(); } diff --git a/changelog.txt b/changelog.txt index d291f48..803c3df 100644 --- a/changelog.txt +++ b/changelog.txt @@ -20,6 +20,7 @@ On Windows some validation tool tips are not aligned right but otherwise the layout is arranged correctly. + Palette visualizer form: OK/Cancel buttons + Color visualizer form: OK/Cancel buttons ++ Image visualizer form: OK/Cancel buttons * Manual zooming is enabled even if Auto Zoom is on; this turns off Auto Zoom automatically. * Turning smoothing of zoomed images on/off affects also shrunk images (previously affected enlarged images only). - Resize: From ba4308c28469f3de62e09ad8c51540c90ede4f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 27 May 2021 20:55:30 +0200 Subject: [PATCH 068/211] Fixing possible concurrency issues --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 3 - .../Model/AsyncTaskBase.cs | 64 ------------------ KGySoft.Drawing.ImagingTools/Res.cs | 3 - .../System/Threading/ManualResetEventSlim.cs | 65 +++++++++++++++++++ .../View/Forms/CountColorsForm.cs | 14 ++-- .../View/Forms/MvvmBaseForm.cs | 26 +++++++- .../View/UserControls/MvvmBaseUserControl.cs | 35 ++++++++-- .../ViewModel/CountColorsViewModel.cs | 29 +++++---- changelog.txt | 1 + 9 files changed, 141 insertions(+), 99 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/System/Threading/ManualResetEventSlim.cs diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 8c93976..36f214e 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -228,9 +228,6 @@ Counting colors... - - Operation has been canceled. - {{Width={0}, Height={1}}} diff --git a/KGySoft.Drawing.ImagingTools/Model/AsyncTaskBase.cs b/KGySoft.Drawing.ImagingTools/Model/AsyncTaskBase.cs index 587eee0..d243705 100644 --- a/KGySoft.Drawing.ImagingTools/Model/AsyncTaskBase.cs +++ b/KGySoft.Drawing.ImagingTools/Model/AsyncTaskBase.cs @@ -28,70 +28,6 @@ namespace KGySoft.Drawing.ImagingTools.Model /// internal abstract class AsyncTaskBase : IDisposable { - #region Nested classes -#if NET35 - - private sealed class ManualResetEventSlim : IDisposable - { - #region Fields - - private readonly object syncRoot = new object(); - - private bool isSet; - private bool isDisposed; - - #endregion - - #region Methods - - #region Public Methods - - public void Dispose() - { - lock (syncRoot) - { - if (isDisposed) - return; - isDisposed = true; - isSet = true; - Monitor.PulseAll(syncRoot); - } - } - - #endregion - - #region Internal Methods - - internal void Set() - { - lock (syncRoot) - { - if (isDisposed) - return; - isSet = true; - Monitor.PulseAll(syncRoot); - } - } - - internal void Wait() - { - lock (syncRoot) - { - if (isDisposed) - return; - while (!isSet) - Monitor.Wait(syncRoot); - } - } - - #endregion - - #endregion - } - -#endif - #endregion - #region Fields #region Internal Fields diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 01d7e06..bd0c58d 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -143,9 +143,6 @@ internal static class Res /// Counting colors... internal static string TextCountingColorsId => "Text_CountingColors"; - /// Operation has been canceled. - internal static string TextOperationCanceledId => "Text_OperationCanceled"; - /// Color Count: {0} internal static string TextColorCountId => "Text_ColorCountFormat"; diff --git a/KGySoft.Drawing.ImagingTools/System/Threading/ManualResetEventSlim.cs b/KGySoft.Drawing.ImagingTools/System/Threading/ManualResetEventSlim.cs new file mode 100644 index 0000000..29182a0 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/System/Threading/ManualResetEventSlim.cs @@ -0,0 +1,65 @@ +#if NET35 + +// ReSharper disable once CheckNamespace +namespace System.Threading +{ + internal sealed class ManualResetEventSlim : IDisposable + { + #region Fields + + private readonly object syncRoot = new object(); + + private bool isSet; + private bool isDisposed; + + #endregion + + #region Methods + + #region Public Methods + + public void Dispose() + { + lock (syncRoot) + { + if (isDisposed) + return; + isDisposed = true; + isSet = true; + Monitor.PulseAll(syncRoot); + } + } + + #endregion + + #region Internal Methods + + internal void Set() + { + lock (syncRoot) + { + if (isDisposed) + return; + isSet = true; + Monitor.PulseAll(syncRoot); + } + } + + internal void Wait() + { + lock (syncRoot) + { + if (isDisposed) + return; + while (!isSet) + Monitor.Wait(syncRoot); + } + } + + #endregion + + #endregion + } +} + +#endif \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.cs index 7f01d3e..82a6610 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.cs @@ -95,15 +95,11 @@ private void InitPropertyBindings() // VM.DisplayText <-> lblCountColorsStatus.Text CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.DisplayText), nameof(lblCountColorsStatus.Text), lblCountColorsStatus); - // in lock because it is already running - lock (ViewModel.ProgressSyncRoot) - { - // VM.IsProcessing -> progress.ProgressVisible - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.IsProcessing), nameof(progress.ProgressVisible), progress); - - // VM.Progress -> progress.Progress - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Progress), nameof(progress.Progress), progress); - } + // VM.IsProcessing -> progress.ProgressVisible + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.IsProcessing), nameof(progress.ProgressVisible), progress); + + // VM.Progress -> progress.Progress + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Progress), nameof(progress.Progress), progress); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index 29669b8..49ddde2 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -18,6 +18,7 @@ using System; using System.Drawing; +using System.Threading; using System.Windows.Forms; using KGySoft.ComponentModel; @@ -32,6 +33,9 @@ internal partial class MvvmBaseForm : BaseForm, IView { #region Fields + private readonly int threadId; + private readonly ManualResetEventSlim handleCreated; + private bool isClosing; private bool isLoaded; private bool isRtlChanging; @@ -63,6 +67,8 @@ internal partial class MvvmBaseForm : BaseForm, IView protected MvvmBaseForm(TViewModel viewModel) { + threadId = Thread.CurrentThread.ManagedThreadId; + handleCreated = new ManualResetEventSlim(false); ApplyRightToLeft(); InitializeComponent(); @@ -130,6 +136,12 @@ protected virtual void ApplyViewModel() VM.ViewLoaded(); } + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated(e); + handleCreated.Set(); + } + protected override void OnFormClosing(FormClosingEventArgs e) { // Changing RightToLeft causes the dialog close. We let it happen because the parent may also change, @@ -174,12 +186,20 @@ private void InvokeIfRequired(Action action) { if (isClosing || Disposing || IsDisposed) return; + try { - if (InvokeRequired) - Invoke(action); - else + // no invoke is required (not using InvokeRequired because that may return false if handle is not created yet) + if (threadId == Thread.CurrentThread.ManagedThreadId) + { action.Invoke(); + return; + } + + if (!handleCreated.IsSet) + handleCreated.Wait(); + + Invoke(action); } catch (ObjectDisposedException) { diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/MvvmBaseUserControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/MvvmBaseUserControl.cs index 4cc77c6..4955145 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/MvvmBaseUserControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/MvvmBaseUserControl.cs @@ -18,7 +18,7 @@ using System; using System.Diagnostics.CodeAnalysis; - +using System.Threading; using KGySoft.ComponentModel; using KGySoft.Drawing.ImagingTools.ViewModel; @@ -31,6 +31,9 @@ internal class MvvmBaseUserControl : BaseUserControl { #region Fields + private readonly int threadId; + private readonly ManualResetEventSlim handleCreated; + private TViewModel? vm; private bool isLoaded; @@ -79,6 +82,8 @@ internal TViewModel ViewModel protected MvvmBaseUserControl() { CommandBindings = new WinFormsCommandBindingsCollection(); + threadId = Thread.CurrentThread.ManagedThreadId; + handleCreated = new ManualResetEventSlim(false); } #endregion @@ -117,6 +122,12 @@ protected virtual void ApplyViewModel() vmb.ViewLoaded(); } + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated(e); + handleCreated.Set(); + } + protected override void Dispose(bool disposing) { if (disposing) @@ -131,10 +142,26 @@ protected override void Dispose(bool disposing) private void InvokeIfRequired(Action action) { - if (InvokeRequired) + if (Disposing || IsDisposed) + return; + + try + { + // no invoke is required (not using InvokeRequired because that may return false if handle is not created yet) + if (threadId == Thread.CurrentThread.ManagedThreadId) + { + action.Invoke(); + return; + } + + if (!handleCreated.IsSet) + handleCreated.Wait(); Invoke(action); - else - action.Invoke(); + } + catch (ObjectDisposedException) + { + // it can happen that actual Invoke is started to execute only after querying isClosing and when Disposing and IsDisposed both return false + } } #endregion diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs index da91db5..654e029 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs @@ -50,10 +50,11 @@ private sealed class CountTask : AsyncTaskBase #region Fields private readonly DrawingProgressManager drawingProgressManager; + private readonly Bitmap bitmap; private volatile CountTask? task; private int? colorCount; - private string displayTextId; + private string displayTextId = default!; private object[]? displayTextArgs; #endregion @@ -73,15 +74,9 @@ private sealed class CountTask : AsyncTaskBase internal CountColorsViewModel(Bitmap bitmap) { - if (bitmap == null) - throw new ArgumentNullException(nameof(bitmap), PublicResources.ArgumentNull); + this.bitmap = bitmap ?? throw new ArgumentNullException(nameof(bitmap), PublicResources.ArgumentNull); SetDisplayText(Res.TextCountingColorsId); - drawingProgressManager = new DrawingProgressManager(p => - { - lock (ProgressSyncRoot) - Progress = p; - }); - BeginCountColors(bitmap); + drawingProgressManager = new DrawingProgressManager(p => Progress = p); } #endregion @@ -117,13 +112,19 @@ internal void CancelIfRunning() protected override bool AffectsModifiedState(string propertyName) => false; + internal override void ViewLoaded() + { + base.ViewLoaded(); + BeginCountColors(); + } + protected override void ApplyDisplayLanguage() => UpdateDisplayText(); #endregion #region Private Methods - private void BeginCountColors(Bitmap bitmap) + private void BeginCountColors() { IsProcessing = true; task = new CountTask { Bitmap = bitmap }; @@ -166,15 +167,17 @@ private void DoCountColors(object? state) if (task.IsCanceled) colorCount = null; - SetModified(colorCount.HasValue); + // returning if task was canceled because cancel closes the UI + if (colorCount.HasValue) + SetModified(true); + else + return; // the execution of this method will be marshaled back to the UI thread void Action() { if (error != null) SetDisplayText(Res.ErrorMessageId, error.Message); - else if (colorCount == null) - SetDisplayText(Res.TextOperationCanceledId); else SetDisplayText(Res.TextColorCountId, colorCount.Value); IsProcessing = false; diff --git a/changelog.txt b/changelog.txt index 803c3df..f67cabf 100644 --- a/changelog.txt +++ b/changelog.txt @@ -23,6 +23,7 @@ + Image visualizer form: OK/Cancel buttons * Manual zooming is enabled even if Auto Zoom is on; this turns off Auto Zoom automatically. * Turning smoothing of zoomed images on/off affects also shrunk images (previously affected enlarged images only). +- Color Count: Result was not always shown (and progress bar was not removed) if the operation ended very quickly. - Resize: - Preventing that invalid sizes replace current text to 0 - Changing error provider associated controls so the layout is not messed up on Linux From 138526cb612ee703a99833fe428234656420378f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 28 May 2021 16:27:23 +0200 Subject: [PATCH 069/211] Fixing grid appearance with high contrast themes --- .../View/Forms/EditResourcesForm.Designer.cs | 36 ++++++++++++------- .../View/Forms/EditResourcesForm.cs | 16 +++++++-- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index 5e02816..21c8ddb 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -32,6 +32,7 @@ private void InitializeComponent() this.components = new System.ComponentModel.Container(); System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle(); this.gbResourceEntries = new System.Windows.Forms.GroupBox(); this.gridResources = new System.Windows.Forms.DataGridView(); this.colResourceKey = new System.Windows.Forms.DataGridViewTextBoxColumn(); @@ -62,7 +63,7 @@ private void InitializeComponent() this.gbResourceEntries.Dock = System.Windows.Forms.DockStyle.Fill; this.gbResourceEntries.Location = new System.Drawing.Point(3, 49); this.gbResourceEntries.Name = "gbResourceEntries"; - this.gbResourceEntries.Size = new System.Drawing.Size(578, 112); + this.gbResourceEntries.Size = new System.Drawing.Size(578, 117); this.gbResourceEntries.TabIndex = 2; this.gbResourceEntries.TabStop = false; this.gbResourceEntries.Text = "gbResourceEntries"; @@ -72,26 +73,35 @@ private void InitializeComponent() this.gridResources.AllowUserToAddRows = false; this.gridResources.AllowUserToDeleteRows = false; dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.ControlLight; + dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.ControlText; this.gridResources.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1; this.gridResources.AutoGenerateColumns = false; - this.gridResources.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; - this.gridResources.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { - this.colResourceKey, - this.colOriginalText, - this.colTranslatedText}); - this.gridResources.DataSource = this.resourceEntryBindingSource; dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; - dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window; + dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Control; dataGridViewCellStyle2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.ControlText; dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight; dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText; dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.gridResources.DefaultCellStyle = dataGridViewCellStyle2; + this.gridResources.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2; + this.gridResources.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.gridResources.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { + this.colResourceKey, + this.colOriginalText, + this.colTranslatedText}); + this.gridResources.DataSource = this.resourceEntryBindingSource; + dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; + dataGridViewCellStyle3.BackColor = System.Drawing.SystemColors.Window; + dataGridViewCellStyle3.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + dataGridViewCellStyle3.ForeColor = System.Drawing.SystemColors.WindowText; + dataGridViewCellStyle3.SelectionBackColor = System.Drawing.SystemColors.Highlight; + dataGridViewCellStyle3.SelectionForeColor = System.Drawing.SystemColors.HighlightText; + dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.gridResources.DefaultCellStyle = dataGridViewCellStyle3; this.gridResources.Dock = System.Windows.Forms.DockStyle.Fill; this.gridResources.Location = new System.Drawing.Point(3, 16); this.gridResources.Name = "gridResources"; - this.gridResources.Size = new System.Drawing.Size(572, 93); + this.gridResources.Size = new System.Drawing.Size(572, 98); this.gridResources.TabIndex = 3; // // colResourceKey @@ -145,7 +155,7 @@ private void InitializeComponent() // splitterEditResources // this.splitterEditResources.Dock = System.Windows.Forms.DockStyle.Bottom; - this.splitterEditResources.Location = new System.Drawing.Point(3, 161); + this.splitterEditResources.Location = new System.Drawing.Point(3, 166); this.splitterEditResources.MinExtra = 50; this.splitterEditResources.MinSize = 50; this.splitterEditResources.Name = "splitterEditResources"; @@ -161,7 +171,7 @@ private void InitializeComponent() this.pnlEditResourceEntry.Controls.Add(this.gbOriginalText, 0, 0); this.pnlEditResourceEntry.Controls.Add(this.gbTranslatedText, 1, 0); this.pnlEditResourceEntry.Dock = System.Windows.Forms.DockStyle.Bottom; - this.pnlEditResourceEntry.Location = new System.Drawing.Point(3, 164); + this.pnlEditResourceEntry.Location = new System.Drawing.Point(3, 169); this.pnlEditResourceEntry.Name = "pnlEditResourceEntry"; this.pnlEditResourceEntry.RowCount = 1; this.pnlEditResourceEntry.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); @@ -217,7 +227,7 @@ private void InitializeComponent() // this.okCancelApplyButtons.ApplyButtonVisible = true; this.okCancelApplyButtons.Dock = System.Windows.Forms.DockStyle.Bottom; - this.okCancelApplyButtons.Location = new System.Drawing.Point(3, 268); + this.okCancelApplyButtons.Location = new System.Drawing.Point(3, 273); this.okCancelApplyButtons.Name = "okCancelApplyButtons"; this.okCancelApplyButtons.Size = new System.Drawing.Size(578, 35); this.okCancelApplyButtons.TabIndex = 1; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs index 6a3effe..54ea97b 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs @@ -16,7 +16,9 @@ #region Usings +using System; using System.Collections.Generic; +using System.Drawing; using System.Windows.Forms; using KGySoft.Drawing.ImagingTools.Model; @@ -36,8 +38,10 @@ internal EditResourcesForm(EditResourcesViewModel viewModel) : base(viewModel) { // Note: Not setting Accept/CancelButton because they would be very annoying during the editing InitializeComponent(); - cmbResourceFiles.ValueMember = nameof(KeyValuePair.Key); - cmbResourceFiles.DisplayMember = nameof(KeyValuePair.Value); + if (SystemInformation.HighContrast) + gridResources.AlternatingRowsDefaultCellStyle = null; + cmbResourceFiles.ValueMember = nameof(KeyValuePair.Key); + cmbResourceFiles.DisplayMember = nameof(KeyValuePair.Value); } #endregion @@ -70,6 +74,14 @@ protected override void ApplyViewModel() base.ApplyViewModel(); } + protected override void OnSystemColorsChanged(EventArgs e) + { + base.OnSystemColorsChanged(e); + gridResources.AlternatingRowsDefaultCellStyle = SystemInformation.HighContrast + ? null + : new DataGridViewCellStyle { BackColor = SystemColors.ControlLight, ForeColor = SystemColors.ControlText }; + } + #endregion #region Private Methods From 77b7cf34709e5ad4600d373a10bf8dedb6e2c6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 29 May 2021 14:35:26 +0200 Subject: [PATCH 070/211] Renaming ResourceOwner to ResourceLibrary --- .../_Enums/ResourceLibrary.cs} | 6 +-- .../ViewModel/EditResourcesViewModel.cs | 54 +++++++++---------- .../ViewModel/LanguageSettingsViewModel.cs | 14 +++++ .../_Classes/ResHelper.cs | 21 ++++---- 4 files changed, 55 insertions(+), 40 deletions(-) rename KGySoft.Drawing.ImagingTools/{_Enums/ResourceOwner.cs => Model/_Enums/ResourceLibrary.cs} (86%) diff --git a/KGySoft.Drawing.ImagingTools/_Enums/ResourceOwner.cs b/KGySoft.Drawing.ImagingTools/Model/_Enums/ResourceLibrary.cs similarity index 86% rename from KGySoft.Drawing.ImagingTools/_Enums/ResourceOwner.cs rename to KGySoft.Drawing.ImagingTools/Model/_Enums/ResourceLibrary.cs index 9327601..ba6f7f5 100644 --- a/KGySoft.Drawing.ImagingTools/_Enums/ResourceOwner.cs +++ b/KGySoft.Drawing.ImagingTools/Model/_Enums/ResourceLibrary.cs @@ -1,7 +1,7 @@ #region Copyright /////////////////////////////////////////////////////////////////////////////// -// File: ResourceOwner.cs +// File: ResourceLibrary.cs /////////////////////////////////////////////////////////////////////////////// // Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved // @@ -14,9 +14,9 @@ #endregion -namespace KGySoft.Drawing.ImagingTools +namespace KGySoft.Drawing.ImagingTools.Model { - internal enum ResourceOwner + internal enum ResourceLibrary { CoreLibraries, DrawingLibraries, diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index 3e70448..4ed41e7 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -60,15 +60,15 @@ internal class EditResourcesViewModel : ViewModelBase private readonly CultureInfo culture; private readonly bool useInvariant; - private readonly Dictionary ResourceSet, bool IsModified)> resources; + private readonly Dictionary ResourceSet, bool IsModified)> resources; #endregion #region Properties - internal KeyValuePair[] ResourceFiles { get; } // get only because never changes + internal KeyValuePair[] ResourceFiles { get; } // get only because never changes internal string TitleCaption { get => Get(); set => Set(value); } - internal ResourceOwner SelectedLibrary { get => Get(); set => Set(value); } + internal ResourceLibrary SelectedLibrary { get => Get(); set => Set(value); } internal IList SelectedSet { get => Get>(); set => Set(value); } internal ICommand ApplyResourcesCommand => Get(() => new SimpleCommand(OnApplyResourcesCommand)); @@ -87,12 +87,12 @@ internal EditResourcesViewModel(CultureInfo culture) // The default language is used as the invariant resource set. // The invariant file name is preferred, unless only the language-specific file exists. - useInvariant = Equals(culture, Res.DefaultLanguage) && !File.Exists(ToFileNameWithPath(ResourceOwner.DrawingTools)); - resources = new Dictionary, bool)>(3, EnumComparer.Comparer); - ResourceFiles = Enum.GetValues().Select(owner => new KeyValuePair(owner, ToFileName(owner))).ToArray(); + useInvariant = Equals(culture, Res.DefaultLanguage) && !File.Exists(ToFileNameWithPath(ResourceLibrary.DrawingTools)); + resources = new Dictionary, bool)>(3, EnumComparer.Comparer); + ResourceFiles = Enum.GetValues().Select(lib => new KeyValuePair(lib, ToFileName(lib))).ToArray(); ApplyResourcesCommandState.Enabled = !Equals(LanguageSettings.DisplayLanguage, culture); UpdateTitle(); - SelectedLibrary = ResourceOwner.DrawingTools; + SelectedLibrary = ResourceLibrary.DrawingTools; } #endregion @@ -109,7 +109,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) switch (e.PropertyName) { case nameof(SelectedLibrary): - UpdateSelectedResources((ResourceOwner)e.NewValue!); + UpdateSelectedResources((ResourceLibrary)e.NewValue!); break; } } @@ -134,23 +134,23 @@ protected override void Dispose(bool disposing) private void UpdateTitle() => TitleCaption = Res.TitleEditResources($"{culture.EnglishName} ({culture.NativeName})"); - private string ToFileName(ResourceOwner owner) => useInvariant - ? ResHelper.GetBaseName(owner) + ".resx" - : $"{ResHelper.GetBaseName(owner)}.{culture.Name}.resx"; + private string ToFileName(ResourceLibrary library) => useInvariant + ? ResHelper.GetBaseName(library) + ".resx" + : $"{ResHelper.GetBaseName(library)}.{culture.Name}.resx"; - private string ToFileNameWithPath(ResourceOwner owner) => Path.Combine(Res.ResourcesDir, ToFileName(owner)); + private string ToFileNameWithPath(ResourceLibrary library) => Path.Combine(Res.ResourcesDir, ToFileName(library)); - private void UpdateSelectedResources(ResourceOwner owner) + private void UpdateSelectedResources(ResourceLibrary library) { - if (resources.TryGetValue(owner, out var value)) + if (resources.TryGetValue(library, out var value)) { SelectedSet = value.ResourceSet; return; } - if (!TryReadResources(owner, out IList? set, out Exception? error)) + if (!TryReadResources(library, out IList? set, out Exception? error)) { - if (!Confirm(Res.ConfirmMessageTryRegenerateResource(ToFileName(owner), error.Message))) + if (!Confirm(Res.ConfirmMessageTryRegenerateResource(ToFileName(library), error.Message))) { SelectedSet = Reflector.EmptyArray(); return; @@ -158,35 +158,35 @@ private void UpdateSelectedResources(ResourceOwner owner) try { - File.Delete(ToFileNameWithPath(owner)); + File.Delete(ToFileNameWithPath(library)); } catch (Exception e) when (!e.IsCritical()) { - ShowError(Res.ErrorMessageFailedToRegenerateResource(ToFileName(owner), error.Message)); + ShowError(Res.ErrorMessageFailedToRegenerateResource(ToFileName(library), error.Message)); SelectedSet = Reflector.EmptyArray(); return; } - if (!TryReadResources(owner, out set, out error)) + if (!TryReadResources(library, out set, out error)) { - ShowError(Res.ErrorMessageFailedToRegenerateResource(ToFileName(owner), error.Message)); + ShowError(Res.ErrorMessageFailedToRegenerateResource(ToFileName(library), error.Message)); SelectedSet = Reflector.EmptyArray(); return; } } - resources[owner] = (set, false); + resources[library] = (set, false); SelectedSet = set; } - private bool TryReadResources(ResourceOwner owner, [MaybeNullWhen(false)]out IList set, [MaybeNullWhen(true)]out Exception error) + private bool TryReadResources(ResourceLibrary library, [MaybeNullWhen(false)]out IList set, [MaybeNullWhen(true)]out Exception error) { try { // Creating a local resource manager so we can generate the entries that currently found in the compiled resource set. // Auto appending only the queried keys so we can add the missing ones since the last creation and also remove the possibly removed ones. // Note that this will not generate any .resx files as we use the default AutoSave = None - using var resourceManger = new DynamicResourceManager(ResHelper.GetBaseName(owner), ResHelper.GetAssembly(owner)) + using var resourceManger = new DynamicResourceManager(ResHelper.GetBaseName(library), ResHelper.GetAssembly(library)) { SafeMode = true, Source = ResourceManagerSources.CompiledOnly, @@ -209,10 +209,10 @@ private bool TryReadResources(ResourceOwner owner, [MaybeNullWhen(false)]out ILi return; ApplyResourcesCommandState.Enabled = true; - if (!resources.TryGetValue(owner, out var value) || value.IsModified) + if (!resources.TryGetValue(library, out var value) || value.IsModified) return; - resources[owner] = (value.ResourceSet, true); + resources[library] = (value.ResourceSet, true); SetModified(true); }; result.ApplySort(nameof(ResourceEntry.Key), ListSortDirection.Ascending); @@ -228,7 +228,7 @@ private bool TryReadResources(ResourceOwner owner, [MaybeNullWhen(false)]out ILi } } - private bool TrySaveResources(ResourceOwner owner, IList set, [MaybeNullWhen(true)]out Exception error) + private bool TrySaveResources(ResourceLibrary library, IList set, [MaybeNullWhen(true)]out Exception error) { // Note: We do not use a DynamicResourceManager for saving. This works because we let the actual DRMs drop their content after saving. try @@ -237,7 +237,7 @@ private bool TrySaveResources(ResourceOwner owner, IList set, [Ma foreach (ResourceEntry res in set) resx.SetObject(res.Key, res.TranslatedText); - resx.Save(ToFileNameWithPath(owner)); + resx.Save(ToFileNameWithPath(library)); error = null; return true; } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index 0b79c97..9c483b6 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -54,6 +54,7 @@ internal class LanguageSettingsViewModel : ViewModelBase internal ICommand ApplyCommand => Get(() => new SimpleCommand(OnApplyCommand)); internal ICommand SaveConfigCommand => Get(() => new SimpleCommand(OnSaveConfigCommand)); internal ICommand EditResourcesCommand => Get(() => new SimpleCommand(OnEditResourcesCommand)); + internal ICommand DownloadResourcesCommand => Get(() => new SimpleCommand(OnDownloadResourcesCommand)); internal ICommandState ApplyCommandState => Get(() => new CommandState()); internal ICommandState EditResourcesCommandState => Get(() => new CommandState()); @@ -243,6 +244,19 @@ private void OnEditResourcesCommand() UpdateApplyCommandState(); } + private void OnDownloadResourcesCommand() + { + using IViewModel viewModel = ViewModelFactory.CreateDownloadResources(); + ShowChildViewCallback?.Invoke(viewModel); + + // If the language was edited, then enabling apply even if it was disabled + dirtyCulture = viewModel.IsModified ? CurrentLanguage : null; + availableResXLanguages = null; + selectableLanguages = null; + ResetLanguages(); + UpdateApplyCommandState(); + } + #endregion #endregion diff --git a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs index 0580021..c3683bf 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs @@ -24,6 +24,7 @@ using KGySoft.Collections; using KGySoft.CoreLibraries; +using KGySoft.Drawing.ImagingTools.Model; using KGySoft.Reflection; using KGySoft.Resources; @@ -95,20 +96,20 @@ internal static HashSet GetAvailableLanguages() } } - internal static string GetBaseName(ResourceOwner owner) => owner switch + internal static string GetBaseName(ResourceLibrary library) => library switch { - ResourceOwner.CoreLibraries => coreLibrariesBaseName, - ResourceOwner.DrawingLibraries => drawingLibrariesBaseName, - ResourceOwner.DrawingTools => drawingToolsBaseName, - _ => throw new ArgumentOutOfRangeException(nameof(owner), PublicResources.EnumOutOfRange(owner)) + ResourceLibrary.CoreLibraries => coreLibrariesBaseName, + ResourceLibrary.DrawingLibraries => drawingLibrariesBaseName, + ResourceLibrary.DrawingTools => drawingToolsBaseName, + _ => throw new ArgumentOutOfRangeException(nameof(library), PublicResources.EnumOutOfRange(library)) }; - internal static Assembly GetAssembly(ResourceOwner owner) => owner switch + internal static Assembly GetAssembly(ResourceLibrary library) => library switch { - ResourceOwner.CoreLibraries => typeof(LanguageSettings).Assembly, - ResourceOwner.DrawingLibraries => typeof(DrawingModule).Assembly, - ResourceOwner.DrawingTools => typeof(Res).Assembly, - _ => throw new ArgumentOutOfRangeException(nameof(owner), PublicResources.EnumOutOfRange(owner)) + ResourceLibrary.CoreLibraries => typeof(LanguageSettings).Assembly, + ResourceLibrary.DrawingLibraries => typeof(DrawingModule).Assembly, + ResourceLibrary.DrawingTools => typeof(Res).Assembly, + _ => throw new ArgumentOutOfRangeException(nameof(library), PublicResources.EnumOutOfRange(library)) }; /// From f137f2578eef4c94ff8664304059f7d818c7a3fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 29 May 2021 14:38:28 +0200 Subject: [PATCH 071/211] Extracting a generic base from ProgressStatusStrip --- .../View/Controls/AdvancedToolStrip.cs | 1 + ...ner.cs => ProgressStatusStrip.Designer.cs} | 2 +- .../View/Controls/ProgressStatusStrip.cs | 178 ++++++++++++++++++ .../ViewModel/CountColorsViewModel.cs | 1 - 4 files changed, 180 insertions(+), 2 deletions(-) rename KGySoft.Drawing.ImagingTools/View/Controls/{DrawingProgressStatusStrip.Designer.cs => ProgressStatusStrip.Designer.cs} (97%) create mode 100644 KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index f0d821b..e274725 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -20,6 +20,7 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; + using KGySoft.Drawing.ImagingTools.View.Components; using KGySoft.Drawing.ImagingTools.WinApi; using KGySoft.Reflection; diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.Designer.cs similarity index 97% rename from KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.Designer.cs rename to KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.Designer.cs index c823f54..e4219a3 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.Designer.cs @@ -6,7 +6,7 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls { - partial class DrawingProgressStatusStrip + partial class ProgressStatusStrip { private void InitializeComponent() { diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.cs new file mode 100644 index 0000000..73c3fd3 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.cs @@ -0,0 +1,178 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: ProgressStatusStrip.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2020 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Windows.Forms; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View.Controls +{ + internal partial class ProgressStatusStrip : StatusStrip + { + #region Fields + + private readonly object syncRoot = new object(); + private readonly bool visualStyles = Application.RenderWithVisualStyles; + + private bool progressVisible = true; // so ctor change will have effect at run-time + private TProgress? progress; + + #endregion + + #region Properties + + #region Internal Properties + + [SuppressMessage("ReSharper", "LocalizableElement", Justification = "Whitespace")] + [SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "ReSharper issue")] + internal virtual bool ProgressVisible + { + get => progressVisible; + set + { + if (progressVisible == value) + return; + progressVisible = value; + if (value) + { + Progress = default; + UpdateProgress(); + } + + // In Windows we don't make label invisible but changing text to a space to prevent status strip height change + if (OSUtils.IsWindows) + { + pbProgress.Visible = progressVisible; + if (!progressVisible) + lblProgress.Text = " "; + } + // On Linux we let the progress bar remain visible for the same reason and to prevent (sort of) appearing ugly thick black areas + else + { + lblProgress.Visible = progressVisible; + if (!progressVisible) + { + pbProgress.Style = ProgressBarStyle.Blocks; + pbProgress.Value = 0; + } + + AdjustSize(); + } + + timer.Enabled = value; + } + } + + internal TProgress? Progress + { + get + { + lock (syncRoot) + return progress; + } + set + { + lock (syncRoot) + progress = value; + } + } + + #endregion + + #region Protected Properties + + protected virtual void UpdateProgress() => throw new InvalidOperationException(Res.InternalError($"{nameof(UpdateProgress)} is not overridden")); + + protected string ProgressText { set => lblProgress.Text = value; } + protected ProgressBarStyle ProgressStyle { set => pbProgress.Style = value; } + protected int Maximum { set => pbProgress.Maximum = value; } + + protected int Value + { + set + { + // Workaround for progress bar on Vista and above where it advances very slow + if (OSUtils.IsVistaOrLater && visualStyles && value > pbProgress.Value && value < pbProgress.Maximum) + pbProgress.Value = value + 1; + pbProgress.Value = value; + } + } + + #endregion + + #endregion + + #region Constructors + + public ProgressStatusStrip() + { + InitializeComponent(); + if (DesignMode) + return; + this.FixAppearance(); + ProgressVisible = false; + SizeChanged += DrawingProgressStatusStrip_SizeChanged; + lblProgress.TextChanged += lblProgress_TextChanged; + lblProgress.VisibleChanged += lblProgress_VisibleChanged; + timer.Tick += timer_Tick; + } + + #endregion + + #region Methods + + #region Protected Methods + + protected override void Dispose(bool disposing) + { + if (disposing) + components?.Dispose(); + + SizeChanged -= DrawingProgressStatusStrip_SizeChanged; + lblProgress.TextChanged -= lblProgress_TextChanged; + lblProgress.VisibleChanged -= lblProgress_VisibleChanged; + timer.Tick -= timer_Tick; + base.Dispose(disposing); + } + + #endregion + + #region Private Methods + + private void AdjustSize() => + pbProgress.Width = Width - (lblProgress.Visible ? lblProgress.Width - lblProgress.Margin.Horizontal : 0) - pbProgress.Margin.Horizontal - 2; + + #endregion + + #region Event handlers +#pragma warning disable IDE1006 // Naming Styles + + private void lblProgress_TextChanged(object? sender, EventArgs e) => AdjustSize(); + private void lblProgress_VisibleChanged(object? sender, EventArgs e) => AdjustSize(); + private void DrawingProgressStatusStrip_SizeChanged(object? sender, EventArgs e) => AdjustSize(); + + private void timer_Tick(object? sender, EventArgs e) => UpdateProgress(); + +#pragma warning restore IDE1006 // Naming Styles + #endregion + + #endregion + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs index 654e029..9914e9f 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs @@ -61,7 +61,6 @@ private sealed class CountTask : AsyncTaskBase #region Properties - internal object ProgressSyncRoot => drawingProgressManager; internal bool IsProcessing { get => Get(); set => Set(value); } internal DrawingProgress Progress { get => Get(); set => Set(value); } internal string DisplayText { get => Get(); set => Set(value); } From abf976415d6ca3715f580766bff7edc2d3774904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 30 May 2021 21:00:21 +0200 Subject: [PATCH 072/211] Refactoring --- ...esourceLibrary.cs => ResourceLibraries.cs} | 18 +++- .../View/Forms/EditResourcesForm.Designer.cs | 14 +-- .../View/Forms/EditResourcesForm.cs | 16 +-- .../ViewModel/CountColorsViewModel.cs | 97 ++++++++++--------- .../ViewModel/EditResourcesViewModel.cs | 32 +++--- 5 files changed, 98 insertions(+), 79 deletions(-) rename KGySoft.Drawing.ImagingTools/Model/_Enums/{ResourceLibrary.cs => ResourceLibraries.cs} (74%) diff --git a/KGySoft.Drawing.ImagingTools/Model/_Enums/ResourceLibrary.cs b/KGySoft.Drawing.ImagingTools/Model/_Enums/ResourceLibraries.cs similarity index 74% rename from KGySoft.Drawing.ImagingTools/Model/_Enums/ResourceLibrary.cs rename to KGySoft.Drawing.ImagingTools/Model/_Enums/ResourceLibraries.cs index ba6f7f5..06257f3 100644 --- a/KGySoft.Drawing.ImagingTools/Model/_Enums/ResourceLibrary.cs +++ b/KGySoft.Drawing.ImagingTools/Model/_Enums/ResourceLibraries.cs @@ -1,7 +1,7 @@ #region Copyright /////////////////////////////////////////////////////////////////////////////// -// File: ResourceLibrary.cs +// File: ResourceLibraries.cs /////////////////////////////////////////////////////////////////////////////// // Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved // @@ -14,12 +14,20 @@ #endregion +#region Usings + +using System; + +#endregion + namespace KGySoft.Drawing.ImagingTools.Model { - internal enum ResourceLibrary + [Flags] + internal enum ResourceLibraries { - CoreLibraries, - DrawingLibraries, - DrawingTools + None, + CoreLibraries = 1, + DrawingLibraries = 1 << 1, + ImagingTools = 1 << 2 } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index 21c8ddb..cfad2e5 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -38,7 +38,7 @@ private void InitializeComponent() this.colResourceKey = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colOriginalText = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colTranslatedText = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.resourceEntryBindingSource = new System.Windows.Forms.BindingSource(this.components); + this.bindingSource = new System.Windows.Forms.BindingSource(this.components); this.gbResourceFile = new System.Windows.Forms.GroupBox(); this.cmbResourceFiles = new System.Windows.Forms.ComboBox(); this.splitterEditResources = new System.Windows.Forms.Splitter(); @@ -50,7 +50,7 @@ private void InitializeComponent() this.okCancelApplyButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); this.gbResourceEntries.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.gridResources)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.resourceEntryBindingSource)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.bindingSource)).BeginInit(); this.gbResourceFile.SuspendLayout(); this.pnlEditResourceEntry.SuspendLayout(); this.gbOriginalText.SuspendLayout(); @@ -89,7 +89,7 @@ private void InitializeComponent() this.colResourceKey, this.colOriginalText, this.colTranslatedText}); - this.gridResources.DataSource = this.resourceEntryBindingSource; + this.gridResources.DataSource = this.bindingSource; dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; dataGridViewCellStyle3.BackColor = System.Drawing.SystemColors.Window; dataGridViewCellStyle3.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); @@ -126,9 +126,9 @@ private void InitializeComponent() this.colTranslatedText.Name = "colTranslatedText"; this.colTranslatedText.Width = 200; // - // resourceEntryBindingSource + // bindingSource // - this.resourceEntryBindingSource.DataSource = typeof(KGySoft.Drawing.ImagingTools.Model.ResourceEntry); + this.bindingSource.DataSource = typeof(KGySoft.Drawing.ImagingTools.Model.ResourceEntry); // // gbResourceFile // @@ -249,7 +249,7 @@ private void InitializeComponent() this.Text = "EditResourcesForm"; this.gbResourceEntries.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.gridResources)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.resourceEntryBindingSource)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.bindingSource)).EndInit(); this.gbResourceFile.ResumeLayout(false); this.pnlEditResourceEntry.ResumeLayout(false); this.gbOriginalText.ResumeLayout(false); @@ -271,7 +271,7 @@ private void InitializeComponent() private System.Windows.Forms.Splitter splitterEditResources; private System.Windows.Forms.TextBox txtOriginalText; private System.Windows.Forms.TextBox txtTranslatedText; - private System.Windows.Forms.BindingSource resourceEntryBindingSource; + private System.Windows.Forms.BindingSource bindingSource; private System.Windows.Forms.GroupBox gbResourceFile; private System.Windows.Forms.ComboBox cmbResourceFiles; private System.Windows.Forms.DataGridViewTextBoxColumn colResourceKey; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs index 54ea97b..adbcddc 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs @@ -40,8 +40,8 @@ internal EditResourcesForm(EditResourcesViewModel viewModel) : base(viewModel) InitializeComponent(); if (SystemInformation.HighContrast) gridResources.AlternatingRowsDefaultCellStyle = null; - cmbResourceFiles.ValueMember = nameof(KeyValuePair.Key); - cmbResourceFiles.DisplayMember = nameof(KeyValuePair.Value); + cmbResourceFiles.ValueMember = nameof(KeyValuePair.Key); + cmbResourceFiles.DisplayMember = nameof(KeyValuePair.Value); } #endregion @@ -97,14 +97,14 @@ private void InitPropertyBindings() // VM.SelectedLibrary <-> cmbResourceFiles.SelectedValue CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.SelectedLibrary), cmbResourceFiles, nameof(cmbResourceFiles.SelectedValue)); - // VM.SelectedSet -> resourceEntryBindingSource.DataSource - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.SelectedSet), nameof(resourceEntryBindingSource.DataSource), resourceEntryBindingSource); + // VM.SelectedSet -> bindingSource.DataSource + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.SelectedSet), nameof(bindingSource.DataSource), bindingSource); - // resourceEntryBindingSource.OriginalText -> txtOriginalText.Text - txtOriginalText.DataBindings.Add(nameof(txtOriginalText.Text), resourceEntryBindingSource, nameof(ResourceEntry.OriginalText), false, DataSourceUpdateMode.Never); + // bindingSource.OriginalText -> txtOriginalText.Text + txtOriginalText.DataBindings.Add(nameof(txtOriginalText.Text), bindingSource, nameof(ResourceEntry.OriginalText), false, DataSourceUpdateMode.Never); - // resourceEntryBindingSource.TranslatedText <-> txtTranslatedText.Text - txtTranslatedText.DataBindings.Add(nameof(txtTranslatedText.Text), resourceEntryBindingSource, nameof(ResourceEntry.TranslatedText), false, DataSourceUpdateMode.OnValidation); + // bindingSource.TranslatedText <-> txtTranslatedText.Text + txtTranslatedText.DataBindings.Add(nameof(txtTranslatedText.Text), bindingSource, nameof(ResourceEntry.TranslatedText), false, DataSourceUpdateMode.OnValidation); } private void InitCommandBindings() diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs index 9914e9f..9cd192c 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs @@ -52,7 +52,7 @@ private sealed class CountTask : AsyncTaskBase private readonly DrawingProgressManager drawingProgressManager; private readonly Bitmap bitmap; - private volatile CountTask? task; + private volatile CountTask? activeTask; private int? colorCount; private string displayTextId = default!; private object[]? displayTextArgs; @@ -86,7 +86,7 @@ internal CountColorsViewModel(Bitmap bitmap) public int? GetEditedModel() { - task?.WaitForCompletion(); + activeTask?.WaitForCompletion(); return colorCount; } @@ -96,7 +96,7 @@ internal CountColorsViewModel(Bitmap bitmap) internal void CancelIfRunning() { - CountTask? t = task; + CountTask? t = activeTask; if (t == null) return; @@ -126,63 +126,70 @@ internal override void ViewLoaded() private void BeginCountColors() { IsProcessing = true; - task = new CountTask { Bitmap = bitmap }; - ThreadPool.QueueUserWorkItem(DoCountColors); + activeTask = new CountTask { Bitmap = bitmap }; + ThreadPool.QueueUserWorkItem(DoCountColors, activeTask); } private void DoCountColors(object? state) { Exception? error = null; + var task = (CountTask)state!; - // We must lock on the image to avoid the possible "bitmap region is already in use" error from the Paint of main view's image viewer, - // which also locks on the image to help avoiding this error - lock (task!.Bitmap) + try { - IReadableBitmapData? bitmapData = null; - try + // We must lock on the image to avoid the possible "bitmap region is already in use" error from the Paint of main view's image viewer, + // which also locks on the image to help avoiding this error + lock (task.Bitmap) { - bitmapData = task.Bitmap.GetReadableBitmapData(); - IAsyncResult asyncResult = bitmapData.BeginGetColorCount(new AsyncConfig + IReadableBitmapData? bitmapData = null; + try { - IsCancelRequestedCallback = () => task.IsCanceled, - ThrowIfCanceled = false, - Progress = drawingProgressManager - }); - - // Waiting to be finished or canceled. As we are on a different thread blocking wait is alright - colorCount = asyncResult.EndGetColorCount(); - } - catch (Exception e) when (!e.IsCriticalGdi()) - { - error = e; - } - finally - { - bitmapData?.Dispose(); - task.SetCompleted(); + bitmapData = task.Bitmap.GetReadableBitmapData(); + IAsyncResult asyncResult = bitmapData.BeginGetColorCount(new AsyncConfig + { + IsCancelRequestedCallback = () => task.IsCanceled, + ThrowIfCanceled = false, + Progress = drawingProgressManager + }); + + // Waiting to be finished or canceled. As we are on a different thread blocking wait is alright + colorCount = asyncResult.EndGetColorCount(); + } + catch (Exception e) when (!e.IsCriticalGdi()) + { + error = e; + } + finally + { + bitmapData?.Dispose(); + task.SetCompleted(); + } } - } - if (task.IsCanceled) - colorCount = null; + if (task.IsCanceled) + colorCount = null; - // returning if task was canceled because cancel closes the UI - if (colorCount.HasValue) - SetModified(true); - else - return; + // returning if task was canceled because cancel closes the UI + if (colorCount.HasValue) + SetModified(true); + else + return; - // the execution of this method will be marshaled back to the UI thread - void Action() + // applying result (or error) + TryInvokeSync(() => + { + if (error != null) + SetDisplayText(Res.ErrorMessageId, error.Message); + else + SetDisplayText(Res.TextColorCountId, colorCount.Value); + IsProcessing = false; + }); + } + finally { - if (error != null) - SetDisplayText(Res.ErrorMessageId, error.Message); - else - SetDisplayText(Res.TextColorCountId, colorCount.Value); - IsProcessing = false; + task.Dispose(); + activeTask = null; } - - TryInvokeSync(Action); } private void SetDisplayText(string resourceId, params object[] args) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index 4ed41e7..90bafcb 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -60,15 +60,15 @@ internal class EditResourcesViewModel : ViewModelBase private readonly CultureInfo culture; private readonly bool useInvariant; - private readonly Dictionary ResourceSet, bool IsModified)> resources; + private readonly Dictionary ResourceSet, bool IsModified)> resources; #endregion #region Properties - internal KeyValuePair[] ResourceFiles { get; } // get only because never changes + internal KeyValuePair[] ResourceFiles { get; } // get only because never changes internal string TitleCaption { get => Get(); set => Set(value); } - internal ResourceLibrary SelectedLibrary { get => Get(); set => Set(value); } + internal ResourceLibraries SelectedLibrary { get => Get(); set => Set(value); } internal IList SelectedSet { get => Get>(); set => Set(value); } internal ICommand ApplyResourcesCommand => Get(() => new SimpleCommand(OnApplyResourcesCommand)); @@ -87,12 +87,12 @@ internal EditResourcesViewModel(CultureInfo culture) // The default language is used as the invariant resource set. // The invariant file name is preferred, unless only the language-specific file exists. - useInvariant = Equals(culture, Res.DefaultLanguage) && !File.Exists(ToFileNameWithPath(ResourceLibrary.DrawingTools)); - resources = new Dictionary, bool)>(3, EnumComparer.Comparer); - ResourceFiles = Enum.GetValues().Select(lib => new KeyValuePair(lib, ToFileName(lib))).ToArray(); + useInvariant = Equals(culture, Res.DefaultLanguage) && !File.Exists(ToFileNameWithPath(ResourceLibraries.ImagingTools)); + resources = new Dictionary, bool)>(3, EnumComparer.Comparer); + ResourceFiles = Enum.GetValues().Select(lib => new KeyValuePair(lib, ToFileName(lib))).ToArray(); ApplyResourcesCommandState.Enabled = !Equals(LanguageSettings.DisplayLanguage, culture); UpdateTitle(); - SelectedLibrary = ResourceLibrary.DrawingTools; + SelectedLibrary = ResourceLibraries.ImagingTools; } #endregion @@ -109,7 +109,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) switch (e.PropertyName) { case nameof(SelectedLibrary): - UpdateSelectedResources((ResourceLibrary)e.NewValue!); + UpdateSelectedResources((ResourceLibraries)e.NewValue!); break; } } @@ -134,13 +134,13 @@ protected override void Dispose(bool disposing) private void UpdateTitle() => TitleCaption = Res.TitleEditResources($"{culture.EnglishName} ({culture.NativeName})"); - private string ToFileName(ResourceLibrary library) => useInvariant + private string ToFileName(ResourceLibraries library) => useInvariant ? ResHelper.GetBaseName(library) + ".resx" : $"{ResHelper.GetBaseName(library)}.{culture.Name}.resx"; - private string ToFileNameWithPath(ResourceLibrary library) => Path.Combine(Res.ResourcesDir, ToFileName(library)); + private string ToFileNameWithPath(ResourceLibraries library) => Path.Combine(Res.ResourcesDir, ToFileName(library)); - private void UpdateSelectedResources(ResourceLibrary library) + private void UpdateSelectedResources(ResourceLibraries library) { if (resources.TryGetValue(library, out var value)) { @@ -179,7 +179,7 @@ private void UpdateSelectedResources(ResourceLibrary library) SelectedSet = set; } - private bool TryReadResources(ResourceLibrary library, [MaybeNullWhen(false)]out IList set, [MaybeNullWhen(true)]out Exception error) + private bool TryReadResources(ResourceLibraries library, [MaybeNullWhen(false)]out IList set, [MaybeNullWhen(true)]out Exception error) { try { @@ -228,7 +228,7 @@ private bool TryReadResources(ResourceLibrary library, [MaybeNullWhen(false)]out } } - private bool TrySaveResources(ResourceLibrary library, IList set, [MaybeNullWhen(true)]out Exception error) + private bool TrySaveResources(ResourceLibraries library, IList set, [MaybeNullWhen(true)]out Exception error) { // Note: We do not use a DynamicResourceManager for saving. This works because we let the actual DRMs drop their content after saving. try @@ -237,7 +237,11 @@ private bool TrySaveResources(ResourceLibrary library, IList set, foreach (ResourceEntry res in set) resx.SetObject(res.Key, res.TranslatedText); - resx.Save(ToFileNameWithPath(library)); + string fileName = ToFileNameWithPath(library); + string dirName = Path.GetDirectoryName(fileName)!; + if (!Directory.Exists(dirName)) + Directory.CreateDirectory(dirName); + resx.Save(fileName); error = null; return true; } From 291ad8bc2bb25c0f3642d6205d8e8c54ffcbbd5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 30 May 2021 21:00:56 +0200 Subject: [PATCH 073/211] Confirm dialog with cancel option --- KGySoft.Drawing.ImagingTools/View/Dialogs.cs | 9 +++++++++ KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs | 3 ++- .../View/UserControls/MvvmBaseUserControl.cs | 5 ++++- KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs | 2 ++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs index 1c42dd0..5e0818d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs +++ b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs @@ -31,9 +31,18 @@ internal static class Dialogs internal static void ErrorMessage(string message) => Show(message, Res.TitleError, MessageBoxButtons.OK, MessageBoxIcon.Error); internal static void InfoMessage(string message) => Show(message, Res.TitleInformation, MessageBoxButtons.OK, MessageBoxIcon.Information); internal static void WarningMessage(string message) => Show(message, Res.TitleWarning, MessageBoxButtons.OK, MessageBoxIcon.Warning); + internal static bool ConfirmMessage(string message, bool isYesDefault = true) => Show(message, Res.TitleConfirmation, MessageBoxButtons.YesNo, MessageBoxIcon.Question, isYesDefault ? MessageBoxDefaultButton.Button1 : MessageBoxDefaultButton.Button2) == DialogResult.Yes; + internal static bool? CancellableConfirmMessage(string message, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1) + => Show(message, Res.TitleConfirmation, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, defaultButton) switch + { + DialogResult.Yes => true, + DialogResult.No => false, + _ => null + }; + #endregion #region Private Methods diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index 49ddde2..18aa7e1 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -68,7 +68,7 @@ internal partial class MvvmBaseForm : BaseForm, IView protected MvvmBaseForm(TViewModel viewModel) { threadId = Thread.CurrentThread.ManagedThreadId; - handleCreated = new ManualResetEventSlim(false); + handleCreated = new ManualResetEventSlim(); ApplyRightToLeft(); InitializeComponent(); @@ -82,6 +82,7 @@ protected MvvmBaseForm(TViewModel viewModel) vm.ShowWarningCallback = Dialogs.WarningMessage; vm.ShowErrorCallback = Dialogs.ErrorMessage; vm.ConfirmCallback = Dialogs.ConfirmMessage; + vm.CancellableConfirmCallback = (msg, btn) => Dialogs.CancellableConfirmMessage(msg, btn switch { 0 => MessageBoxDefaultButton.Button1, 1 => MessageBoxDefaultButton.Button2, _ => MessageBoxDefaultButton.Button3 }); vm.ShowChildViewCallback = ShowChildView; vm.CloseViewCallback = () => BeginInvoke(new Action(Close)); vm.SynchronizedInvokeCallback = InvokeIfRequired; diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/MvvmBaseUserControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/MvvmBaseUserControl.cs index 4955145..c361df5 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/MvvmBaseUserControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/MvvmBaseUserControl.cs @@ -19,6 +19,8 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Threading; +using System.Windows.Forms; + using KGySoft.ComponentModel; using KGySoft.Drawing.ImagingTools.ViewModel; @@ -83,7 +85,7 @@ protected MvvmBaseUserControl() { CommandBindings = new WinFormsCommandBindingsCollection(); threadId = Thread.CurrentThread.ManagedThreadId; - handleCreated = new ManualResetEventSlim(false); + handleCreated = new ManualResetEventSlim(); } #endregion @@ -117,6 +119,7 @@ protected virtual void ApplyViewModel() vmb.ShowWarningCallback = Dialogs.WarningMessage; vmb.ShowErrorCallback = Dialogs.ErrorMessage; vmb.ConfirmCallback = Dialogs.ConfirmMessage; + vmb.CancellableConfirmCallback = (msg, btn) => Dialogs.CancellableConfirmMessage(msg, btn switch { 0 => MessageBoxDefaultButton.Button1, 1 => MessageBoxDefaultButton.Button2, _ => MessageBoxDefaultButton.Button3 }); vmb.SynchronizedInvokeCallback = InvokeIfRequired; vmb.ViewLoaded(); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs index 85366a9..fdb055b 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs @@ -35,6 +35,7 @@ internal abstract class ViewModelBase : ObservableObjectBase, IViewModel internal Action? ShowWarningCallback { get => Get?>(); set => Set(value); } internal Action? ShowInfoCallback { get => Get?>(); set => Set(value); } internal Func? ConfirmCallback { get => Get?>(); set => Set(value); } + internal Func? CancellableConfirmCallback { get => Get?>(); set => Set(value); } internal Action? ShowChildViewCallback { get => Get?>(); set => Set(value); } internal Action? CloseViewCallback { get => Get(); set => Set(value); } internal Action? SynchronizedInvokeCallback { private get => Get?>(); set => Set(value); } @@ -55,6 +56,7 @@ internal abstract class ViewModelBase : ObservableObjectBase, IViewModel protected void ShowWarning(string message) => ShowWarningCallback?.Invoke(message); protected void ShowInfo(string message) => ShowInfoCallback?.Invoke(message); protected bool Confirm(string message, bool isYesDefault = true) => ConfirmCallback?.Invoke(message, isYesDefault) ?? true; + protected bool? CancellableConfirm(string message, int defaultButton = 0) => CancellableConfirmCallback?.Invoke(message, defaultButton); protected bool TryInvokeSync(Action action) { From 24d2e0cec0a011861b7da9d3e90237c42bd7b74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 30 May 2021 21:01:28 +0200 Subject: [PATCH 074/211] Download resources --- KGySoft.Drawing.ImagingTools/App.config | 6 + ...KGySoft.Drawing.ImagingTools.Messages.resx | 18 + .../KGySoft.Drawing.ImagingTools.csproj | 3 +- .../Model/LocalizationInfo.cs | 39 ++ ...wModel.DownloadableResourceItem.datasource | 10 + KGySoft.Drawing.ImagingTools/Res.cs | 31 +- .../CallerMemberNameAttribute.cs | 10 + .../System/Threading/ManualResetEventSlim.cs | 13 +- .../Controls/DownloadProgressStatusStrip.cs | 60 +++ .../Controls/DrawingProgressStatusStrip.cs | 118 +----- .../View/Controls/ProgressStatusStrip.cs | 3 + .../Forms/DownloadResourcesForm.Designer.cs | 154 +++++++ .../View/Forms/DownloadResourcesForm.cs | 126 ++++++ .../Forms/LanguageSettingsForm.Designer.cs | 46 ++- .../View/Forms/LanguageSettingsForm.cs | 5 +- .../View/ViewFactory.cs | 1 + .../ViewModel/DownloadResourcesViewModel.cs | 384 ++++++++++++++++++ .../ViewModel/DownloadableResourceItem.cs | 57 +++ .../DownloadableResourceItemCollection.cs | 80 ++++ .../ViewModel/LanguageSettingsViewModel.cs | 19 +- .../ViewModel/ViewModelFactory.cs | 24 +- .../_Classes/Configuration.cs | 173 ++++++++ .../_Classes/ResHelper.cs | 24 +- KGySoft.Drawing.Tools.sln.DotSettings | 1 + changelog.txt | 1 + 25 files changed, 1248 insertions(+), 158 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/App.config create mode 100644 KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs create mode 100644 KGySoft.Drawing.ImagingTools/Properties/DataSources/KGySoft.Drawing.ImagingTools.ViewModel.DownloadableResourceItem.datasource create mode 100644 KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/CallerMemberNameAttribute.cs create mode 100644 KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressStatusStrip.cs create mode 100644 KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs create mode 100644 KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs create mode 100644 KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs create mode 100644 KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs create mode 100644 KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItemCollection.cs create mode 100644 KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs diff --git a/KGySoft.Drawing.ImagingTools/App.config b/KGySoft.Drawing.ImagingTools/App.config new file mode 100644 index 0000000..3b07eba --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 36f214e..eb47f41 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -201,6 +201,9 @@ Color Count: {0:N0} + + Downloading... + Toggles whether the animation is handled as a single image. • When checked, animation will play and saving as GIF saves the whole animation. @@ -387,6 +390,12 @@ Either select at least '{1}' or reduce the number of colors to {2}. Failed to save resource file {0}: {1} + + Failed to access online resources: {0} + + + Failed to download resource file {0}: {1} + Could not create directory {0}: {1} @@ -430,6 +439,12 @@ Are you sure you want to save the file with the provided extension? Failed to read resource file {0}: {1} Do you want to try to regenerate it? The current file will be deleted. + + + The following files already exist: +{0} + +Do you want to overwrite them? Are you sure you want to remove this installation? @@ -479,6 +494,9 @@ Dithering may help to preserve more details. The selected pixel format represents a narrower set of colors than the original '{0}'. Dithering may help to preserve more details. + + {0} file(s) have been downloaded. + N/A (KGySoft.Drawing.DebuggerVisualizers.dll is missing) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj index 744e7c6..3116953 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj @@ -2,7 +2,7 @@ - net45 + net35 false KGySoft.Drawing.ImagingTools bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml @@ -33,6 +33,7 @@ + diff --git a/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs b/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs new file mode 100644 index 0000000..8704266 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs @@ -0,0 +1,39 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: LocalizationInfo.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +using System.Globalization; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.Model +{ + internal class LocalizationInfo + { + #region Properties + + public CultureInfo Language { get; set; } = default!; + public string? Description { get; set; } + public ResourceLibraries ResourceSets { get; set; } = default!; + public Version Version { get; set; } = default!; + public Version ImagingToolsVersion { get; set; } = default!; + public string? Author { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/Properties/DataSources/KGySoft.Drawing.ImagingTools.ViewModel.DownloadableResourceItem.datasource b/KGySoft.Drawing.ImagingTools/Properties/DataSources/KGySoft.Drawing.ImagingTools.ViewModel.DownloadableResourceItem.datasource new file mode 100644 index 0000000..3a8cc20 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/Properties/DataSources/KGySoft.Drawing.ImagingTools.ViewModel.DownloadableResourceItem.datasource @@ -0,0 +1,10 @@ + + + + KGySoft.Drawing.ImagingTools.ViewModel.DownloadableResourceItem, KGySoft.Drawing.ImagingTools, Version=2.3.0.0, Culture=neutral, PublicKeyToken=b45eba277439ddfe + \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index bd0c58d..69bfca8 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -27,7 +27,6 @@ using KGySoft.Collections; using KGySoft.CoreLibraries; -using KGySoft.Drawing.ImagingTools.Properties; using KGySoft.Reflection; using KGySoft.Resources; @@ -146,6 +145,9 @@ internal static class Res /// Color Count: {0} internal static string TextColorCountId => "Text_ColorCountFormat"; + /// Downloading... + internal static string TextDownloading => Get("Text_Downloading"); + #endregion #region Info Texts @@ -195,8 +197,10 @@ internal static class Res /// Are you sure you want to remove this installation? internal static string ConfirmMessageRemoveInstallation => Get("ConfirmMessage_RemoveInstallation"); +#if NETCOREAPP /// You are about to install the .NET Core version, which might not be supported by Visual Studio as a debugger visualizer. Are you sure? - internal static string ConfirmMessageNetCoreVersion => Get("ConfirmMessage_NetCoreVersion"); + internal static string ConfirmMessageNetCoreVersion => Get("ConfirmMessage_NetCoreVersion"); +#endif /// There are unsaved modifications. Are sure to discard the changes? internal static string ConfirmMessageDiscardChanges => Get("ConfirmMessage_DiscardChanges"); @@ -248,9 +252,9 @@ static Res() : CultureInfo.InvariantCulture).GetClosestNeutralCulture(); DrawingModule.Initialize(); - bool allowResXResources = Settings.Default.AllowResXResources; + bool allowResXResources = Configuration.AllowResXResources; CultureInfo desiredDisplayLanguage = allowResXResources - ? Settings.Default.UseOSLanguage ? OSLanguage : Settings.Default.DisplayLanguage // here, allowing specific languages, too + ? Configuration.UseOSLanguage ? OSLanguage : Configuration.DisplayLanguage // here, allowing specific languages, too : DefaultLanguage; LanguageSettings.DisplayLanguage = Equals(desiredDisplayLanguage, CultureInfo.InvariantCulture) ? DefaultLanguage : desiredDisplayLanguage; @@ -488,6 +492,13 @@ internal static string InfoColor(int argb, string knownColors, string systemColo /// Failed to save resource file {0}: {1} internal static string ErrorMessageFailedToSaveResource(string fileName, string message) => Get("ErrorMessage_FailedToSaveResourceFormat", fileName, message); + /// Failed to access online resources: {0} + internal static string ErrorMessageCouldNotAccessOnlineResources(string message) => Get("ErrorMessage_CouldNotAccessOnlineResourcesFormat", message); + + /// Failed to download resource file {0}: {1} + internal static string ErrorMessageFailedToDownloadResource(string fileName, string message) => Get("ErrorMessage_FailedToDownloadResourceFormat", fileName, message); + +#if NETCOREAPP /// Could not create directory {0}: {1} /// /// The debugger visualizer may will not work for .NET Core projects. @@ -501,7 +512,8 @@ internal static string InfoColor(int argb, string knownColors, string systemColo /// Could not copy file {0}: {1} /// /// The debugger visualizer may will not work for .NET Core projects. - internal static string WarningMessageCouldNotCopyFileNetCore(string path, string message) => Get("WarningMessage_CouldNotCopyFileNetCoreFormat", path, message); + internal static string WarningMessageCouldNotCopyFileNetCore(string path, string message) => Get("WarningMessage_CouldNotCopyFileNetCoreFormat", path, message); +#endif /// The installation finished with a warning: {0} internal static string WarningMessageInstallationWarning(string warning) => Get("WarningMessage_InstallationWarningFormat", warning); @@ -525,6 +537,12 @@ internal static string InfoColor(int argb, string knownColors, string systemColo /// Do you want to try to regenerate it? The current file will be deleted. internal static string ConfirmMessageTryRegenerateResource(string fileName, string message) => Get("ConfirmMessage_TryRegenerateResourceFormat", fileName, message); + /// The following files already exist: + /// {0} + /// + /// Do you want to overwrite them? + internal static string ConfirmMessageOverwriteResources(string files) => Get("ConfirmMessage_MessageOverwriteResourcesFormat", files); + /// {0} is the lowest compatible pixel format, which still supports the selected quantizer. internal static string InfoMessagePixelFormatUnnecessarilyWide(PixelFormat pixelFormat) => Get("InfoMessage_PixelFormatUnnecessarilyWideFormat", pixelFormat); @@ -547,6 +565,9 @@ internal static string InfoColor(int argb, string knownColors, string systemColo /// The ditherer is ignored for pixel format '{0}' if there is no quantizer specified. internal static string InfoMessageDithererIgnored(PixelFormat pixelFormat) => Get("InfoMessage_DithererIgnoredFormat", pixelFormat); + /// {0} file(s) have been downloaded. + internal static string InfoMessageDownloadComplete(int count) => Get("InfoMessage_DownloadCompleteFormat", count); + #endregion #region Installations diff --git a/KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/CallerMemberNameAttribute.cs b/KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/CallerMemberNameAttribute.cs new file mode 100644 index 0000000..b6e70fb --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/System/Runtime.CompilerServices/CallerMemberNameAttribute.cs @@ -0,0 +1,10 @@ +#if NET35 || NET40 +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class CallerMemberNameAttribute : Attribute + { + } +} +#endif diff --git a/KGySoft.Drawing.ImagingTools/System/Threading/ManualResetEventSlim.cs b/KGySoft.Drawing.ImagingTools/System/Threading/ManualResetEventSlim.cs index 29182a0..d514a2c 100644 --- a/KGySoft.Drawing.ImagingTools/System/Threading/ManualResetEventSlim.cs +++ b/KGySoft.Drawing.ImagingTools/System/Threading/ManualResetEventSlim.cs @@ -9,9 +9,14 @@ internal sealed class ManualResetEventSlim : IDisposable private readonly object syncRoot = new object(); - private bool isSet; private bool isDisposed; + #endregion + + #region Properties + + internal bool IsSet { get; private set; } + #endregion #region Methods @@ -25,7 +30,7 @@ public void Dispose() if (isDisposed) return; isDisposed = true; - isSet = true; + IsSet = true; Monitor.PulseAll(syncRoot); } } @@ -40,7 +45,7 @@ internal void Set() { if (isDisposed) return; - isSet = true; + IsSet = true; Monitor.PulseAll(syncRoot); } } @@ -51,7 +56,7 @@ internal void Wait() { if (isDisposed) return; - while (!isSet) + while (!IsSet) Monitor.Wait(syncRoot); } } diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressStatusStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressStatusStrip.cs new file mode 100644 index 0000000..c0bef7f --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressStatusStrip.cs @@ -0,0 +1,60 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: DownloadProgressStatusStrip.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2020 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System.Windows.Forms; +using KGySoft.Drawing.ImagingTools.Model; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View.Controls +{ + internal class DownloadProgressStatusStrip : ProgressStatusStrip<(int MaximumValue, int CurrentValue)> + { + #region Properties + + internal override bool ProgressVisible + { + get => base.ProgressVisible; + set + { + if (value) + ProgressText = Res.TextDownloading; + base.ProgressVisible = value; + } + } + + #endregion + + #region Methods + + protected override void UpdateProgress() + { + var progress = Progress; + if (progress.MaximumValue == 0) + ProgressStyle = ProgressBarStyle.Marquee; + else + { + ProgressStyle = ProgressBarStyle.Blocks; + Maximum = progress.MaximumValue; + Value = progress.CurrentValue; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs index f77c9cc..31dd700 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs @@ -16,144 +16,42 @@ #region Usings -using System; -using System.Diagnostics.CodeAnalysis; using System.Windows.Forms; #endregion namespace KGySoft.Drawing.ImagingTools.View.Controls { - internal partial class DrawingProgressStatusStrip : StatusStrip + internal class DrawingProgressStatusStrip : ProgressStatusStrip { #region Fields - private readonly bool visualStyles = Application.RenderWithVisualStyles; - private bool progressVisible = true; // so ctor change will have effect at run-time private DrawingProgress? displayedProgress; #endregion - #region Properties - - [SuppressMessage("ReSharper", "LocalizableElement", Justification = "Whitespace")] - [SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "ReSharper issue")] - internal bool ProgressVisible - { - get => progressVisible; - set - { - if (progressVisible == value) - return; - progressVisible = value; - if (value) - UpdateProgress(default); - - // In Windows we don't make label invisible but changing text to a space to prevent status strip height change - if (OSUtils.IsWindows) - { - pbProgress.Visible = progressVisible; - if (!progressVisible) - lblProgress.Text = " "; - } - // On Linux we let the progress bar remain visible for the same reason and to prevent (sort of) appearing ugly thick black areas - else - { - lblProgress.Visible = progressVisible; - if (!progressVisible) - { - pbProgress.Style = ProgressBarStyle.Blocks; - pbProgress.Value = 0; - } - - AdjustSize(); - } - - timer.Enabled = value; - } - } - - internal DrawingProgress Progress { get; set; } - - #endregion - - #region Constructors - - public DrawingProgressStatusStrip() - { - InitializeComponent(); - if (DesignMode) - return; - this.FixAppearance(); - ProgressVisible = false; - SizeChanged += DrawingProgressStatusStrip_SizeChanged; - lblProgress.TextChanged += lblProgress_TextChanged; - lblProgress.VisibleChanged += lblProgress_VisibleChanged; - timer.Tick += timer_Tick; - } - - #endregion - #region Methods - #region Protected Methods - - protected override void Dispose(bool disposing) - { - if (disposing) - components?.Dispose(); - - SizeChanged -= DrawingProgressStatusStrip_SizeChanged; - lblProgress.TextChanged -= lblProgress_TextChanged; - lblProgress.VisibleChanged -= lblProgress_VisibleChanged; - timer.Tick -= timer_Tick; - base.Dispose(disposing); - } - - #endregion - - #region Private Methods - - private void AdjustSize() => - pbProgress.Width = Width - (lblProgress.Visible ? lblProgress.Width - lblProgress.Margin.Horizontal : 0) - pbProgress.Margin.Horizontal - 2; - - private void UpdateProgress(DrawingProgress progress) + protected override void UpdateProgress() { + DrawingProgress progress = Progress; if (progress == displayedProgress) return; if (displayedProgress?.OperationType != progress.OperationType) - lblProgress.Text = Res.Get(progress.OperationType); + ProgressText = Res.Get(progress.OperationType); if (progress.MaximumValue == 0) - pbProgress.Style = ProgressBarStyle.Marquee; + ProgressStyle = ProgressBarStyle.Marquee; else { - pbProgress.Style = ProgressBarStyle.Blocks; - pbProgress.Maximum = progress.MaximumValue; - - // Workaround for progress bar on Vista and above where it advances very slow - if (OSUtils.IsVistaOrLater && visualStyles && progress.CurrentValue > pbProgress.Value && progress.CurrentValue < progress.MaximumValue) - pbProgress.Value = progress.CurrentValue + 1; - pbProgress.Value = progress.CurrentValue; + ProgressStyle = ProgressBarStyle.Blocks; + Maximum = progress.MaximumValue; + Value = progress.CurrentValue; } displayedProgress = progress; } #endregion - - #region Event handlers -#pragma warning disable IDE1006 // Naming Styles - - private void lblProgress_TextChanged(object? sender, EventArgs e) => AdjustSize(); - private void lblProgress_VisibleChanged(object? sender, EventArgs e) => AdjustSize(); - private void DrawingProgressStatusStrip_SizeChanged(object? sender, EventArgs e) => AdjustSize(); - - private void timer_Tick(object? sender, EventArgs e) => UpdateProgress(Progress); - -#pragma warning restore IDE1006 // Naming Styles - #endregion - - #endregion } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.cs index 73c3fd3..195cc24 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.cs @@ -24,6 +24,9 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls { + /// + /// A with a progress bar that can be updated from any thread. + /// internal partial class ProgressStatusStrip : StatusStrip { #region Fields diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs new file mode 100644 index 0000000..41ce5ac --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs @@ -0,0 +1,154 @@ + +namespace KGySoft.Drawing.ImagingTools.View.Forms +{ + partial class DownloadResourcesForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.bindingSource = new System.Windows.Forms.BindingSource(this.components); + this.okCancelButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); + this.gridDownloadableResources = new System.Windows.Forms.DataGridView(); + this.colSelected = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.colLanguage = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colAuthor = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colVersion = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colImagingToolsVersion = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colDescription = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.progress = new KGySoft.Drawing.ImagingTools.View.Controls.DownloadProgressStatusStrip(); + ((System.ComponentModel.ISupportInitialize)(this.bindingSource)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.gridDownloadableResources)).BeginInit(); + this.SuspendLayout(); + // + // bindingSource + // + this.bindingSource.DataSource = typeof(KGySoft.Drawing.ImagingTools.ViewModel.DownloadableResourceItem); + // + // okCancelButtons + // + this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; + this.okCancelButtons.Location = new System.Drawing.Point(3, 166); + this.okCancelButtons.Name = "okCancelButtons"; + this.okCancelButtons.Size = new System.Drawing.Size(358, 35); + this.okCancelButtons.TabIndex = 1; + // + // gridDownloadableResources + // + this.gridDownloadableResources.AllowUserToAddRows = false; + this.gridDownloadableResources.AllowUserToDeleteRows = false; + this.gridDownloadableResources.AutoGenerateColumns = false; + this.gridDownloadableResources.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.gridDownloadableResources.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { + this.colSelected, + this.colLanguage, + this.colAuthor, + this.colVersion, + this.colImagingToolsVersion, + this.colDescription}); + this.gridDownloadableResources.DataSource = this.bindingSource; + this.gridDownloadableResources.Dock = System.Windows.Forms.DockStyle.Fill; + this.gridDownloadableResources.Location = new System.Drawing.Point(3, 3); + this.gridDownloadableResources.Name = "gridDownloadableResources"; + this.gridDownloadableResources.Size = new System.Drawing.Size(358, 163); + this.gridDownloadableResources.TabIndex = 0; + // + // colSelected + // + this.colSelected.DataPropertyName = "Selected"; + this.colSelected.HeaderText = "colSelected"; + this.colSelected.Name = "colSelected"; + this.colSelected.Width = 50; + // + // colLanguage + // + this.colLanguage.DataPropertyName = "Language"; + this.colLanguage.HeaderText = "colLanguage"; + this.colLanguage.Name = "colLanguage"; + this.colLanguage.ReadOnly = true; + // + // colAuthor + // + this.colAuthor.DataPropertyName = "Author"; + this.colAuthor.HeaderText = "colAuthor"; + this.colAuthor.Name = "colAuthor"; + this.colAuthor.ReadOnly = true; + // + // colVersion + // + this.colVersion.DataPropertyName = "Version"; + this.colVersion.HeaderText = "colVersion"; + this.colVersion.Name = "colVersion"; + this.colVersion.ReadOnly = true; + this.colVersion.Width = 60; + // + // colImagingToolsVersion + // + this.colImagingToolsVersion.DataPropertyName = "ImagingToolsVersion"; + this.colImagingToolsVersion.HeaderText = "colImagingToolsVersion"; + this.colImagingToolsVersion.Name = "colImagingToolsVersion"; + this.colImagingToolsVersion.ReadOnly = true; + this.colImagingToolsVersion.Width = 60; + // + // colDescription + // + this.colDescription.DataPropertyName = "Description"; + this.colDescription.HeaderText = "colDescription"; + this.colDescription.Name = "colDescription"; + this.colDescription.ReadOnly = true; + this.colDescription.Width = 120; + // + // progress + // + this.progress.BackColor = System.Drawing.Color.Transparent; + this.progress.Location = new System.Drawing.Point(3, 201); + this.progress.Name = "progress"; + this.progress.Size = new System.Drawing.Size(358, 22); + this.progress.SizingGrip = false; + this.progress.TabIndex = 2; + this.progress.Text = "drawingProgressStatusStrip1"; + // + // DownloadResourcesForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(364, 226); + this.Controls.Add(this.gridDownloadableResources); + this.Controls.Add(this.okCancelButtons); + this.Controls.Add(this.progress); + this.MinimizeBox = false; + this.MinimumSize = new System.Drawing.Size(350, 250); + this.Name = "DownloadResourcesForm"; + this.Padding = new System.Windows.Forms.Padding(3); + this.Text = "DownloadResourcesForm"; + ((System.ComponentModel.ISupportInitialize)(this.bindingSource)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.gridDownloadableResources)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private Controls.DownloadProgressStatusStrip progress; + private System.Windows.Forms.DataGridView gridDownloadableResources; + private System.Windows.Forms.BindingSource bindingSource; + private UserControls.OkCancelButtons okCancelButtons; + private System.Windows.Forms.DataGridViewCheckBoxColumn colSelected; + private System.Windows.Forms.DataGridViewTextBoxColumn colLanguage; + private System.Windows.Forms.DataGridViewTextBoxColumn colAuthor; + private System.Windows.Forms.DataGridViewTextBoxColumn colVersion; + private System.Windows.Forms.DataGridViewTextBoxColumn colImagingToolsVersion; + private System.Windows.Forms.DataGridViewTextBoxColumn colDescription; + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs new file mode 100644 index 0000000..19e283a --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs @@ -0,0 +1,126 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: DownloadResourcesForm.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System.Windows.Forms; + +using KGySoft.Drawing.ImagingTools.ViewModel; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View.Forms +{ + internal partial class DownloadResourcesForm : MvvmBaseForm + { + #region Constructors + + #region Internal Constructors + + internal DownloadResourcesForm(DownloadResourcesViewModel viewModel) : base(viewModel) + { + InitializeComponent(); + okCancelButtons.OKButton.Name = "btnDownload"; + okCancelButtons.OKButton.DialogResult = DialogResult.None; + AcceptButton = okCancelButtons.OKButton; + CancelButton = okCancelButtons.CancelButton; + } + + #endregion + + #region Private Constructors + + private DownloadResourcesForm() : this(null!) + { + // this ctor is just for the designer + } + + #endregion + + #endregion + + #region Methods + + #region Protected Methods + + protected override void ApplyResources() + { + Icon = Properties.Resources.Language; + base.ApplyResources(); + } + + protected override void ApplyViewModel() + { + InitCommandBindings(); + InitPropertyBindings(); + base.ApplyViewModel(); + } + + protected override void OnFormClosing(FormClosingEventArgs e) + { + ViewModel.CancelIfRunning(); + base.OnFormClosing(e); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + components?.Dispose(); + + base.Dispose(disposing); + } + + #endregion + + #region Private Methods + + private void InitCommandBindings() + { + CommandBindings.Add(ViewModel.DownloadCommand, ViewModel.DownloadCommandState) + .AddSource(okCancelButtons.OKButton, nameof(okCancelButtons.OKButton.Click)); + CommandBindings.Add(ViewModel.CancelCommand) + .AddSource(okCancelButtons.CancelButton, nameof(okCancelButtons.CancelButton.Click)); + CommandBindings.Add(OnDirtyStateChangedCommand) + .AddSource(gridDownloadableResources, nameof(gridDownloadableResources.CurrentCellDirtyStateChanged)); + } + + private void InitPropertyBindings() + { + // VM.IsProcessing -> progress.ProgressVisible + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.IsProcessing), nameof(progress.ProgressVisible), progress); + + // VM.Progress -> progress.Progress + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Progress), nameof(progress.Progress), progress); + + // VM.Items -> bindingSource.DataSource + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Items), nameof(bindingSource.DataSource), bindingSource); + } + + #endregion + + #region Command Handlers + + private void OnDirtyStateChangedCommand() + { + if (gridDownloadableResources.CurrentCell is DataGridViewCheckBoxCell { EditingCellValueChanged: true }) + gridDownloadableResources.CommitEdit(DataGridViewDataErrorContexts.Commit); + } + + #endregion + + #endregion + } +} diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs index 95c75a6..795333b 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs @@ -34,6 +34,7 @@ private void InitializeComponent() this.gbAllowResxResources = new KGySoft.Drawing.ImagingTools.View.Controls.CheckGroupBox(); this.gbDisplayLanguage = new System.Windows.Forms.GroupBox(); this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.btnDownloadResources = new System.Windows.Forms.Button(); this.btnEditResources = new System.Windows.Forms.Button(); this.cmbLanguages = new System.Windows.Forms.ComboBox(); this.chbExistingResourcesOnly = new System.Windows.Forms.CheckBox(); @@ -48,9 +49,9 @@ private void InitializeComponent() // this.okCancelApplyButtons.ApplyButtonVisible = true; this.okCancelApplyButtons.Dock = System.Windows.Forms.DockStyle.Bottom; - this.okCancelApplyButtons.Location = new System.Drawing.Point(3, 108); + this.okCancelApplyButtons.Location = new System.Drawing.Point(3, 137); this.okCancelApplyButtons.Name = "okCancelApplyButtons"; - this.okCancelApplyButtons.Size = new System.Drawing.Size(298, 35); + this.okCancelApplyButtons.Size = new System.Drawing.Size(328, 35); this.okCancelApplyButtons.TabIndex = 1; // // gbAllowResxResources @@ -63,7 +64,7 @@ private void InitializeComponent() this.gbAllowResxResources.Location = new System.Drawing.Point(3, 3); this.gbAllowResxResources.Name = "gbAllowResxResources"; this.gbAllowResxResources.Padding = new System.Windows.Forms.Padding(5); - this.gbAllowResxResources.Size = new System.Drawing.Size(298, 105); + this.gbAllowResxResources.Size = new System.Drawing.Size(328, 134); this.gbAllowResxResources.TabIndex = 0; this.gbAllowResxResources.TabStop = false; this.gbAllowResxResources.Text = "gbAllowResxResources"; @@ -74,7 +75,7 @@ private void InitializeComponent() this.gbDisplayLanguage.Dock = System.Windows.Forms.DockStyle.Fill; this.gbDisplayLanguage.Location = new System.Drawing.Point(5, 54); this.gbDisplayLanguage.Name = "gbDisplayLanguage"; - this.gbDisplayLanguage.Size = new System.Drawing.Size(288, 46); + this.gbDisplayLanguage.Size = new System.Drawing.Size(318, 75); this.gbDisplayLanguage.TabIndex = 2; this.gbDisplayLanguage.TabStop = false; this.gbDisplayLanguage.Text = "gbDisplayLanguage"; @@ -84,21 +85,39 @@ private void InitializeComponent() this.tableLayoutPanel1.ColumnCount = 2; this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.Controls.Add(this.btnDownloadResources, 0, 1); this.tableLayoutPanel1.Controls.Add(this.btnEditResources, 0, 0); this.tableLayoutPanel1.Controls.Add(this.cmbLanguages, 0, 0); this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 16); this.tableLayoutPanel1.Name = "tableLayoutPanel1"; - this.tableLayoutPanel1.RowCount = 1; - this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel1.Size = new System.Drawing.Size(282, 27); + this.tableLayoutPanel1.RowCount = 2; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(312, 56); this.tableLayoutPanel1.TabIndex = 0; // + // btnDownloadResources + // + this.btnDownloadResources.AutoSize = true; + this.tableLayoutPanel1.SetColumnSpan(this.btnDownloadResources, 2); + this.btnDownloadResources.Dock = System.Windows.Forms.DockStyle.Top; + this.btnDownloadResources.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.btnDownloadResources.Location = new System.Drawing.Point(2, 30); + this.btnDownloadResources.Margin = new System.Windows.Forms.Padding(2, 2, 3, 3); + this.btnDownloadResources.MinimumSize = new System.Drawing.Size(0, 23); + this.btnDownloadResources.Name = "btnDownloadResources"; + this.btnDownloadResources.Size = new System.Drawing.Size(307, 23); + this.btnDownloadResources.TabIndex = 2; + this.btnDownloadResources.Text = "btnDownloadResources"; + this.btnDownloadResources.UseVisualStyleBackColor = true; + // // btnEditResources // this.btnEditResources.AutoSize = true; + this.btnEditResources.Dock = System.Windows.Forms.DockStyle.Top; this.btnEditResources.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.btnEditResources.Location = new System.Drawing.Point(174, 2); + this.btnEditResources.Location = new System.Drawing.Point(204, 2); this.btnEditResources.Margin = new System.Windows.Forms.Padding(3, 2, 3, 3); this.btnEditResources.MinimumSize = new System.Drawing.Size(0, 23); this.btnEditResources.Name = "btnEditResources"; @@ -109,13 +128,13 @@ private void InitializeComponent() // // cmbLanguages // - this.cmbLanguages.Dock = System.Windows.Forms.DockStyle.Top; + this.cmbLanguages.Dock = System.Windows.Forms.DockStyle.Fill; this.cmbLanguages.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.cmbLanguages.FlatStyle = System.Windows.Forms.FlatStyle.System; this.cmbLanguages.FormattingEnabled = true; this.cmbLanguages.Location = new System.Drawing.Point(3, 3); this.cmbLanguages.Name = "cmbLanguages"; - this.cmbLanguages.Size = new System.Drawing.Size(165, 21); + this.cmbLanguages.Size = new System.Drawing.Size(195, 21); this.cmbLanguages.TabIndex = 0; // // chbExistingResourcesOnly @@ -125,7 +144,7 @@ private void InitializeComponent() this.chbExistingResourcesOnly.FlatStyle = System.Windows.Forms.FlatStyle.System; this.chbExistingResourcesOnly.Location = new System.Drawing.Point(5, 36); this.chbExistingResourcesOnly.Name = "chbExistingResourcesOnly"; - this.chbExistingResourcesOnly.Size = new System.Drawing.Size(288, 18); + this.chbExistingResourcesOnly.Size = new System.Drawing.Size(318, 18); this.chbExistingResourcesOnly.TabIndex = 1; this.chbExistingResourcesOnly.Text = "chbExistingResourcesOnly"; this.chbExistingResourcesOnly.UseVisualStyleBackColor = true; @@ -137,7 +156,7 @@ private void InitializeComponent() this.chbUseOSLanguage.FlatStyle = System.Windows.Forms.FlatStyle.System; this.chbUseOSLanguage.Location = new System.Drawing.Point(5, 18); this.chbUseOSLanguage.Name = "chbUseOSLanguage"; - this.chbUseOSLanguage.Size = new System.Drawing.Size(288, 18); + this.chbUseOSLanguage.Size = new System.Drawing.Size(318, 18); this.chbUseOSLanguage.TabIndex = 0; this.chbUseOSLanguage.Text = "chbUseOSLanguage"; this.chbUseOSLanguage.UseVisualStyleBackColor = true; @@ -146,7 +165,7 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(304, 151); + this.ClientSize = new System.Drawing.Size(334, 175); this.Controls.Add(this.gbAllowResxResources); this.Controls.Add(this.okCancelApplyButtons); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; @@ -175,5 +194,6 @@ private void InitializeComponent() private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; private System.Windows.Forms.Button btnEditResources; private System.Windows.Forms.ComboBox cmbLanguages; + private System.Windows.Forms.Button btnDownloadResources; } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs index 8bc92ac..11ca58f 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs @@ -93,7 +93,7 @@ private void InitPropertyBindings() // VM.UseOSLanguage <-> chbUseOSLanguage.Checked CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.UseOSLanguage), chbUseOSLanguage, nameof(chbUseOSLanguage.Checked)); - // VM.UseOSLanguage <-> chbUseOSLanguage.Checked + // VM.ExistingLanguagesOnly <-> chbExistingResourcesOnly.Checked CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.ExistingLanguagesOnly), chbExistingResourcesOnly, nameof(chbExistingResourcesOnly.Checked)); // VM.UseOSLanguage -> !cmbLanguages.Enabled @@ -122,6 +122,9 @@ private void InitCommandBindings() CommandBindings.Add(ViewModel.EditResourcesCommand, ViewModel.EditResourcesCommandState) .AddSource(btnEditResources, nameof(btnEditResources.Click)); + + CommandBindings.Add(ViewModel.DownloadResourcesCommand) + .AddSource(btnDownloadResources, nameof(btnDownloadResources.Click)); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs b/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs index d17fb86..a5d0b93 100644 --- a/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs +++ b/KGySoft.Drawing.ImagingTools/View/ViewFactory.cs @@ -70,6 +70,7 @@ public static IView CreateView(IViewModel viewModel) AdjustGammaViewModel adjustGammaViewModel => new AdjustGammaForm(adjustGammaViewModel), LanguageSettingsViewModel languageSettingsViewModel => new LanguageSettingsForm(languageSettingsViewModel), EditResourcesViewModel editResourcesViewModel => new EditResourcesForm(editResourcesViewModel), + DownloadResourcesViewModel downloadResourcesViewModel => new DownloadResourcesForm(downloadResourcesViewModel), _ => throw new InvalidOperationException(Res.InternalError($"Unexpected viewModel type: {viewModel.GetType()}")) }; } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs new file mode 100644 index 0000000..475649a --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -0,0 +1,384 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: DownloadResourcesViewModel.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using System.Xml; + +using KGySoft.ComponentModel; +using KGySoft.CoreLibraries; +using KGySoft.Drawing.ImagingTools.Model; +using KGySoft.Serialization.Xml; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.ViewModel +{ + internal class DownloadResourcesViewModel : ViewModelBase, IViewModel> + { + #region Nested classes + + #region DownloadManifestTask class + + private class DownloadTask : AsyncTaskBase + { + #region Fields + + internal Uri Uri = default!; + + #endregion + } + + #endregion + + #region DownloadResourcesTask class + + private sealed class DownloadResourcesTask : DownloadTask + { + #region Fields + + internal List Files; + internal bool Owerwrite; + + #endregion + } + + #endregion + + #region DownloadInfo class + + private sealed class DownloadInfo + { + #region Fields + + private readonly string remotePath; + + #endregion + + #region Properties + + internal CultureInfo Culture { get; } + internal string FileName { get; } + internal string RemoteUri => $"{remotePath}/{FileName}"; + internal string LocalPath => Path.Combine(Res.ResourcesDir, FileName); + + #endregion + + #region Constructors + + public DownloadInfo(LocalizationInfo info, ResourceLibraries library) + { + Culture = info.Language; + FileName = Equals(info.Language, Res.DefaultLanguage) + ? ResHelper.GetBaseName(library) + ".resx" + : $"{ResHelper.GetBaseName(library)}.{info.Language.Name}.resx"; + remotePath = $"{info.Language.Name}_{info.Author}_{info.Version}"; + } + + #endregion + } + + #endregion + + #endregion + + #region Fields + + private volatile AsyncTaskBase? activeTask; + private volatile HashSet downloadedCultures = new HashSet(); + + #endregion + + #region Properties + + internal DownloadableResourceItemCollection? Items { get => Get(); set => Set(value); } + internal bool IsProcessing { get => Get(); set => Set(value); } + internal (int MaximumValue, int CurrentValue) Progress { get => Get<(int, int)>(); set => Set(value); } + + internal ICommand CancelCommand => Get(() => new SimpleCommand(OnCancelCommand)); + internal ICommand DownloadCommand => Get(() => new SimpleCommand(OnDownloadCommand)); + + internal ICommandState DownloadCommandState => Get(() => new CommandState { Enabled = false }); + + #endregion + + #region Methods + + #region Internal Methods + + internal void CancelIfRunning() + { + AsyncTaskBase? t = activeTask; + if (t == null) + return; + + t.IsCanceled = true; + SetModified(false); + t.WaitForCompletion(); + } + + #endregion + + #region Protected Methods + + protected override bool AffectsModifiedState(string propertyName) => false; + + internal override void ViewLoaded() + { + base.ViewLoaded(); + try + { + BeginDownloadManifest(); + } + catch (Exception e) when (!e.IsCritical()) + { + ShowError(Res.ErrorMessageCouldNotAccessOnlineResources(e.Message)); + CloseViewCallback?.Invoke(); + } + } + + protected override void Dispose(bool disposing) + { + if (IsDisposed) + return; + + if (disposing) + Items?.Dispose(); + + base.Dispose(disposing); + } + + #endregion + + #region Private Methods + + private void BeginDownloadManifest() + { + IsProcessing = true; + activeTask = new DownloadTask { Uri = new Uri(Configuration.BaseUri, "manifest.xml") }; + ThreadPool.QueueUserWorkItem(DoDownloadManifest, activeTask); + } + + private void DoDownloadManifest(object state) + { + var task = (DownloadTask)state!; + try + { + if (task.IsCanceled) + return; + + Progress = (2, 0); + byte[]? data = Download(task); + if (data == null) + return; + + using var reader = XmlReader.Create(new StreamReader(new MemoryStream(data), Encoding.UTF8)); + reader.ReadStartElement("manifest"); + var items = new List(); + XmlSerializer.DeserializeContent(reader, items); + + TryInvokeSync(() => + { + Items = new DownloadableResourceItemCollection(items); + DownloadCommandState.Enabled = true; + IsProcessing = false; + }); + } + catch (Exception e) when (!e.IsCritical()) + { + ShowError(Res.ErrorMessageCouldNotAccessOnlineResources(e.Message)); + TryInvokeSync(() => CloseViewCallback?.Invoke()); + } + finally + { + task.Dispose(); + activeTask = null; + } + } + + private void BeginDownloadResources(List toDownload, bool overwrite) + { + DownloadCommandState.Enabled = false; + IsProcessing = true; + activeTask = new DownloadResourcesTask { Files = toDownload, Owerwrite = overwrite }; + ThreadPool.QueueUserWorkItem(DoDownloadResources, activeTask); + } + + private void DoDownloadResources(object state) + { + var task = (DownloadResourcesTask)state!; + string current = null!; + try + { + if (task.IsCanceled) + return; + + // x3: 2 for download (retrieving response + downloading content), 1 for saving the file + Progress = (task.Files.Count * 3, 0); + int downloaded = 0; + foreach (DownloadInfo downloadInfo in task.Files) + { + current = downloadInfo.FileName; + if (task.IsCanceled) + return; + + if (!task.Owerwrite && File.Exists(downloadInfo.LocalPath)) + { + IncrementProgress(3); + continue; + } + + // downloading in memory first + task.Uri = new Uri(Configuration.BaseUri, downloadInfo.RemoteUri); + byte[]? data = Download(task); + if (data == null) + return; + + // if there was no issue with downloading, then saving the file + File.WriteAllBytes(downloadInfo.LocalPath, data); + downloadedCultures.Add(downloadInfo.Culture); + IncrementProgress(); + downloaded += 1; + SetModified(true); + } + + TryInvokeSync(() => + { + IsProcessing = false; + ShowInfo(Res.InfoMessageDownloadComplete(downloaded)); + CloseViewCallback?.Invoke(); + }); + } + catch (Exception e) when (!e.IsCritical()) + { + // not clearing the downloadedCultures because those files are removed + TryInvokeSync(() => + { + IsProcessing = false; + DownloadCommandState.Enabled = true; + }); + ShowError(Res.ErrorMessageFailedToDownloadResource(current, e.Message)); + } + finally + { + task.Dispose(); + activeTask = null; + } + } + + /// + /// Returns the downloaded content, or null if task was canceled. + /// Always increments 2 in progress: 1 for response, 1 for the downloaded content. + /// + private byte[]? Download(DownloadTask task) + { + // Not using WebClient and its async methods because we are already on a separate thread + // and this way we can use the same thread for multiple files, too. + var request = WebRequest.Create(task.Uri); + using WebResponse response = request.GetResponse(); + if (task.IsCanceled) + return null; + + // We do not use the file size in the progress because + // 1.) We work with small files + // 2.) When we download more files we can't set the maximum value for all of the files + IncrementProgress(); + using Stream? src = response.GetResponseStream(); + if (src == null) + return null; + + int len = (int)response.ContentLength; + var result = new byte[len]; + int offset = 0; + int count; + while ((count = src.Read(result, offset, result.Length - offset)) > 0) + { + if (task.IsCanceled) + return null; + offset += count; + } + + IncrementProgress(); + return result; + } + + private void IncrementProgress(int value = 1) + { + var current = Progress; + Progress = (current.MaximumValue, current.CurrentValue + value); + } + + #endregion + + #region Explicitly Implemented Inteface Methods + + ICollection IViewModel>.GetEditedModel() => downloadedCultures; + + #endregion + + #region Command Handlers + + private void OnCancelCommand() + { + CancelIfRunning(); + CloseViewCallback?.Invoke(); + } + + private void OnDownloadCommand() + { + var toDownload = new List(); + var existingFiles = new List(); + + foreach (DownloadableResourceItem item in Items!) + { + if (!item.Selected) + continue; + + LocalizationInfo info = item.Info; + foreach (ResourceLibraries lib in info.ResourceSets.GetFlags(false)) + { + var file = new DownloadInfo(info, lib); + toDownload.Add(file); + if (File.Exists(file.LocalPath)) + existingFiles.Add(file.FileName); + } + } + + bool overwrite = false; + if (existingFiles.Count > 0) + { + bool? confirmResult = CancellableConfirm(Res.ConfirmMessageOverwriteResources(existingFiles.Join(Environment.NewLine)), 1); + if (confirmResult == null) + return; + + overwrite = confirmResult.Value; + } + + BeginDownloadResources(toDownload, overwrite); + } + + #endregion + + #endregion + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs new file mode 100644 index 0000000..eea6b84 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs @@ -0,0 +1,57 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: DownloadableResourceItem.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using KGySoft.ComponentModel; +using KGySoft.Drawing.ImagingTools.Model; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.ViewModel +{ + internal class DownloadableResourceItem : ObservableObjectBase + { + #region Properties + + #region Public Properties + + public bool Selected { get => Get(); set => Set(value); } + public string Language => $"{Info.Language.EnglishName} ({Info.Language.NativeName})"; + public string? Author => Info.Author; + public string Version => Info.Version.ToString(); + public string ImagingToolsVersion => Info.ImagingToolsVersion.ToString(); + public string? Description => Info.Description; + + #endregion + + #region Internal Properties + + internal LocalizationInfo Info { get; } + + internal string CultureName => Info.Language.Name; + + #endregion + + #endregion + + #region Constructors + + internal DownloadableResourceItem(LocalizationInfo info) => this.Info = info; + + #endregion + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItemCollection.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItemCollection.cs new file mode 100644 index 0000000..a1de97c --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItemCollection.cs @@ -0,0 +1,80 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: DownloadableResourceItemCollection.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System.Collections.Generic; +using System.ComponentModel; + +using KGySoft.Collections; +using KGySoft.ComponentModel; +using KGySoft.Drawing.ImagingTools.Model; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.ViewModel +{ + internal class DownloadableResourceItemCollection : SortableBindingList + { + #region Fields + + private readonly StringKeyedDictionary> langGroups; + + #endregion + + #region Constructors + + internal DownloadableResourceItemCollection(ICollection collection) : base(new List(collection.Count)) + { + langGroups = new StringKeyedDictionary>(); + foreach (LocalizationInfo info in collection) + { + string langKey = info.Language.Name; + var item = new DownloadableResourceItem(info); + item.PropertyChanged += Item_PropertyChanged; + + if (langGroups.TryGetValue(langKey, out List? group)) + group.Add(item); + else + langGroups[langKey] = new List(1) { item }; + + Add(item); + } + + ApplySort(nameof(DownloadableResourceItem.Language), ListSortDirection.Ascending); + } + + #endregion + + #region Methods + + private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + var changedItem = (DownloadableResourceItem)sender; + if (e.PropertyName != nameof(DownloadableResourceItem.Selected) || !changedItem.Selected) + return; + + // deselecting other items of the same language as if they belonged to the same radio group + foreach (DownloadableResourceItem item in langGroups[((DownloadableResourceItem)sender).CultureName]) + { + if (item != sender && item.Selected) + item.Selected = false; + } + } + + #endregion + } +} diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index 9c483b6..36b59fc 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -23,7 +23,6 @@ using System.Globalization; using KGySoft.ComponentModel; -using KGySoft.Drawing.ImagingTools.Properties; using KGySoft.Resources; #endregion @@ -90,8 +89,8 @@ private List SelectableLanguages internal LanguageSettingsViewModel() { CurrentLanguage = LanguageSettings.DisplayLanguage; - AllowResXResources = Settings.Default.AllowResXResources; - UseOSLanguage = Settings.Default.UseOSLanguage; + AllowResXResources = Configuration.AllowResXResources; + UseOSLanguage = Configuration.UseOSLanguage; ExistingLanguagesOnly = true; // could be the default value but this way we spare one reset when initializing binding ResetLanguages(); UpdateApplyCommandState(); @@ -219,12 +218,12 @@ private void OnSaveConfigCommand() OnApplyCommand(); // saving the configuration - Settings.Default.AllowResXResources = AllowResXResources; - Settings.Default.UseOSLanguage = UseOSLanguage; - Settings.Default.DisplayLanguage = CurrentLanguage; + Configuration.AllowResXResources = AllowResXResources; + Configuration.UseOSLanguage = UseOSLanguage; + Configuration.DisplayLanguage = CurrentLanguage; try { - Settings.Default.Save(); + Configuration.SaveSettings(); } catch (Exception e) when (!e.IsCritical()) { @@ -246,11 +245,11 @@ private void OnEditResourcesCommand() private void OnDownloadResourcesCommand() { - using IViewModel viewModel = ViewModelFactory.CreateDownloadResources(); + using IViewModel> viewModel = ViewModelFactory.CreateDownloadResources(); ShowChildViewCallback?.Invoke(viewModel); - // If the language was edited, then enabling apply even if it was disabled - dirtyCulture = viewModel.IsModified ? CurrentLanguage : null; + // If the language was overwritten, then enabling apply even if it was disabled + dirtyCulture = viewModel.IsModified && viewModel.GetEditedModel().Contains(CurrentLanguage) ? CurrentLanguage : null; availableResXLanguages = null; selectableLanguages = null; ResetLanguages(); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs index d8dc559..8f252a7 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs @@ -16,10 +16,18 @@ #region Usings +#if NETFRAMEWORK +using System; +#endif +using System.Collections.Generic; using System.Drawing; - using System.Drawing.Imaging; using System.Globalization; + +#if NETFRAMEWORK +using KGySoft.ComponentModel; +using KGySoft.CoreLibraries; +#endif using KGySoft.Drawing.ImagingTools.Model; #endregion @@ -34,7 +42,13 @@ public static class ViewModelFactory { #region Constructors - static ViewModelFactory() => Res.EnsureInitialized(); + static ViewModelFactory() + { + Res.EnsureInitialized(); +#if NETFRAMEWORK + typeof(Version).RegisterTypeConverter(); +#endif + } #endregion @@ -233,6 +247,12 @@ public static IViewModel FromGraphics(Graphics? graphics) /// An instance that represents a view model for managing language settings. public static IViewModel CreateEditResources(CultureInfo culture) => new EditResourcesViewModel(culture); + /// + /// Creates a view model for downloading resources. + /// + /// An instance that represents a view model for managing language settings. + public static IViewModel> CreateDownloadResources() => new DownloadResourcesViewModel(); + #endregion } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs new file mode 100644 index 0000000..0db040b --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs @@ -0,0 +1,173 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: Configuration.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +#if NET35 +using System.ComponentModel; +#endif +using System.Configuration; +using System.Globalization; +#if NET35 +using System.Reflection; +#endif +using System.Runtime.CompilerServices; + +#if NET35 +using KGySoft.CoreLibraries; +#endif +using KGySoft.Drawing.ImagingTools.Properties; + +#endregion + +namespace KGySoft.Drawing.ImagingTools +{ + internal static class Configuration + { + #region Nested classes + +#if NET35 + /// + /// This class is needed for .NET 3.5, which emits display name of a language instead of the property. + /// + private sealed class CultureInfoConverterFixed : CultureInfoConverter + { + #region Methods + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (value == null) + return String.Empty; + if (value is not CultureInfo ci) + throw new ArgumentException(PublicResources.NotAnInstanceOfType(typeof(CultureInfo))); + + return destinationType == typeof(string) ? ci.Name : base.ConvertTo(context, culture, value, destinationType); + } + + #endregion + } +#endif + + #endregion + + #region Constants + + private const string defaultResourceRepositoryLocation = "https://raw.githubusercontent.com/koszeggy/KGySoft.Drawing.Tools/pages/res/"; + //private const string defaultResourceRepositoryLocation = "https://koszeggy.github.io/KGySoft.Drawing.Tools/res/"; + + #endregion + + #region Fields + + private static Uri? baseUri; + + #endregion + + #region Properties + + #region Internal Properties + + internal static bool AllowResXResources { get => GetFromSettings(); set => SetInSettings(value); } + internal static bool UseOSLanguage { get => GetFromSettings(); set => SetInSettings(value); } + internal static CultureInfo DisplayLanguage { get => GetFromSettings() ?? Res.DefaultLanguage; set => SetInSettings(value); } + internal static Uri BaseUri => baseUri ??= new Uri(ResourceRepositoryLocation); + + #endregion + + #region Private Properties + + private static string ResourceRepositoryLocation => GetFromAppConfig() ?? defaultResourceRepositoryLocation; + + #endregion + + #endregion + + #region Constructors + +#if NET35 + static Configuration() + { + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + typeof(CultureInfo).RegisterTypeConverter(); + } +#endif + + #endregion + + #region Methods + + #region Internal Methods + + internal static void SaveSettings() => Settings.Default.Save(); + + #endregion + + #region Private Methods + + private static T? GetFromSettings([CallerMemberName]string propertyName = null!) + { + try + { + return (T)Settings.Default[propertyName]; + } + catch (Exception e) when (!e.IsCritical()) + { + return default; + } + } + + private static void SetInSettings(object value, [CallerMemberName]string propertyName = null!) + { + try + { + Settings.Default[propertyName] = value; + } + catch (Exception e) when (!e.IsCritical()) + { + } + } + + private static string? GetFromAppConfig([CallerMemberName]string propertyName = null!) + { + try + { + return ConfigurationManager.AppSettings[propertyName]; + } + catch (Exception e) when (!e.IsCritical()) + { + return null; + } + } + + #endregion + + #region Event handlers + +#if NET35 + private static Assembly? CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + if (args.Name.StartsWith("System, Version=", StringComparison.Ordinal)) + return typeof(UserSettingsGroup).Assembly; + return null; + } +#endif + + #endregion + + #endregion + } +} diff --git a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs index c3683bf..4e26467 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs @@ -38,7 +38,7 @@ internal static class ResHelper private const string coreLibrariesBaseName = "KGySoft.CoreLibraries.Messages"; private const string drawingLibrariesBaseName = "KGySoft.Drawing.Messages"; - private const string drawingToolsBaseName = "KGySoft.Drawing.ImagingTools.Messages"; + private const string imagingToolsBaseName = "KGySoft.Drawing.ImagingTools.Messages"; #endregion @@ -75,8 +75,8 @@ internal static HashSet GetAvailableLanguages() if (!Directory.Exists(dir)) return result; - int startIndex = dir.Length + drawingToolsBaseName.Length + 2; - string[] files = Directory.GetFiles(dir, $"{drawingToolsBaseName}.*.resx", SearchOption.TopDirectoryOnly); + int startIndex = dir.Length + imagingToolsBaseName.Length + 2; + string[] files = Directory.GetFiles(dir, $"{imagingToolsBaseName}.*.resx", SearchOption.TopDirectoryOnly); foreach (string file in files) { StringSegment resName = file.AsSegment(startIndex, file.Length - startIndex - 5); @@ -85,7 +85,7 @@ internal static HashSet GetAvailableLanguages() } // checking the invariant resource as it should act as default language - if (!result.Contains(Res.DefaultLanguage) && File.Exists(Path.Combine(dir, $"{drawingToolsBaseName}.resx"))) + if (!result.Contains(Res.DefaultLanguage) && File.Exists(Path.Combine(dir, $"{imagingToolsBaseName}.resx"))) result.Add(Res.DefaultLanguage); return result; } @@ -96,19 +96,19 @@ internal static HashSet GetAvailableLanguages() } } - internal static string GetBaseName(ResourceLibrary library) => library switch + internal static string GetBaseName(ResourceLibraries library) => library switch { - ResourceLibrary.CoreLibraries => coreLibrariesBaseName, - ResourceLibrary.DrawingLibraries => drawingLibrariesBaseName, - ResourceLibrary.DrawingTools => drawingToolsBaseName, + ResourceLibraries.CoreLibraries => coreLibrariesBaseName, + ResourceLibraries.DrawingLibraries => drawingLibrariesBaseName, + ResourceLibraries.ImagingTools => imagingToolsBaseName, _ => throw new ArgumentOutOfRangeException(nameof(library), PublicResources.EnumOutOfRange(library)) }; - internal static Assembly GetAssembly(ResourceLibrary library) => library switch + internal static Assembly GetAssembly(ResourceLibraries library) => library switch { - ResourceLibrary.CoreLibraries => typeof(LanguageSettings).Assembly, - ResourceLibrary.DrawingLibraries => typeof(DrawingModule).Assembly, - ResourceLibrary.DrawingTools => typeof(Res).Assembly, + ResourceLibraries.CoreLibraries => typeof(LanguageSettings).Assembly, + ResourceLibraries.DrawingLibraries => typeof(DrawingModule).Assembly, + ResourceLibraries.ImagingTools => typeof(Res).Assembly, _ => throw new ArgumentOutOfRangeException(nameof(library), PublicResources.EnumOutOfRange(library)) }; diff --git a/KGySoft.Drawing.Tools.sln.DotSettings b/KGySoft.Drawing.Tools.sln.DotSettings index ed204ad..620f173 100644 --- a/KGySoft.Drawing.Tools.sln.DotSettings +++ b/KGySoft.Drawing.Tools.sln.DotSettings @@ -28,4 +28,5 @@ True True True + True True \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index f67cabf..5b5aefc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -35,6 +35,7 @@ + ViewModelFactory class: + New CreateLanguageSettings method + New CreateEditResources method + + New CreateDownloadResources method + IViewModel interface + New IsModified property * IViewModel interface From 1abab071d99cfc055d74e791d86a166d6e59b617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 31 May 2021 10:39:40 +0200 Subject: [PATCH 075/211] Fixes for different platforms --- KGySoft.Drawing.ImagingTools/App.config | 2 +- KGySoft.Drawing.ImagingTools/Res.cs | 2 +- .../Forms/LanguageSettingsForm.Designer.cs | 4 +-- .../View/Forms/LanguageSettingsForm.cs | 1 + .../ViewModel/EditResourcesViewModel.cs | 2 +- .../_Classes/Configuration.cs | 27 +++++++++++++------ changelog.txt | 2 +- 7 files changed, 25 insertions(+), 15 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/App.config b/KGySoft.Drawing.ImagingTools/App.config index 3b07eba..2f81111 100644 --- a/KGySoft.Drawing.ImagingTools/App.config +++ b/KGySoft.Drawing.ImagingTools/App.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 69bfca8..c0c1a24 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -498,7 +498,7 @@ internal static string InfoColor(int argb, string knownColors, string systemColo /// Failed to download resource file {0}: {1} internal static string ErrorMessageFailedToDownloadResource(string fileName, string message) => Get("ErrorMessage_FailedToDownloadResourceFormat", fileName, message); -#if NETCOREAPP +#if NET45 /// Could not create directory {0}: {1} /// /// The debugger visualizer may will not work for .NET Core projects. diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs index 795333b..04d8b30 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs @@ -105,7 +105,6 @@ private void InitializeComponent() this.btnDownloadResources.FlatStyle = System.Windows.Forms.FlatStyle.System; this.btnDownloadResources.Location = new System.Drawing.Point(2, 30); this.btnDownloadResources.Margin = new System.Windows.Forms.Padding(2, 2, 3, 3); - this.btnDownloadResources.MinimumSize = new System.Drawing.Size(0, 23); this.btnDownloadResources.Name = "btnDownloadResources"; this.btnDownloadResources.Size = new System.Drawing.Size(307, 23); this.btnDownloadResources.TabIndex = 2; @@ -119,7 +118,6 @@ private void InitializeComponent() this.btnEditResources.FlatStyle = System.Windows.Forms.FlatStyle.System; this.btnEditResources.Location = new System.Drawing.Point(204, 2); this.btnEditResources.Margin = new System.Windows.Forms.Padding(3, 2, 3, 3); - this.btnEditResources.MinimumSize = new System.Drawing.Size(0, 23); this.btnEditResources.Name = "btnEditResources"; this.btnEditResources.Size = new System.Drawing.Size(105, 23); this.btnEditResources.TabIndex = 1; @@ -128,7 +126,7 @@ private void InitializeComponent() // // cmbLanguages // - this.cmbLanguages.Dock = System.Windows.Forms.DockStyle.Fill; + this.cmbLanguages.Dock = System.Windows.Forms.DockStyle.Top; this.cmbLanguages.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.cmbLanguages.FlatStyle = System.Windows.Forms.FlatStyle.System; this.cmbLanguages.FormattingEnabled = true; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs index 11ca58f..cf38a8c 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs @@ -35,6 +35,7 @@ internal partial class LanguageSettingsForm : MvvmBaseForm, bool)>(3, EnumComparer.Comparer); - ResourceFiles = Enum.GetValues().Select(lib => new KeyValuePair(lib, ToFileName(lib))).ToArray(); + ResourceFiles = Enum.GetFlags().Select(lib => new KeyValuePair(lib, ToFileName(lib))).ToArray(); ApplyResourcesCommandState.Enabled = !Equals(LanguageSettings.DisplayLanguage, culture); UpdateTitle(); SelectedLibrary = ResourceLibraries.ImagingTools; diff --git a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs index 0db040b..74c1f94 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs @@ -22,9 +22,10 @@ #endif using System.Configuration; using System.Globalization; -#if NET35 -using System.Reflection; +#if NETFRAMEWORK +using System.Net; #endif +using System.Reflection; using System.Runtime.CompilerServices; #if NET35 @@ -66,8 +67,7 @@ private sealed class CultureInfoConverterFixed : CultureInfoConverter #region Constants - private const string defaultResourceRepositoryLocation = "https://raw.githubusercontent.com/koszeggy/KGySoft.Drawing.Tools/pages/res/"; - //private const string defaultResourceRepositoryLocation = "https://koszeggy.github.io/KGySoft.Drawing.Tools/res/"; + private const string defaultResourceRepositoryLocation = "https://koszeggy.github.io/KGySoft.Drawing.Tools/res/"; // same as "https://raw.githubusercontent.com/koszeggy/KGySoft.Drawing.Tools/pages/res/" #endregion @@ -98,13 +98,21 @@ private sealed class CultureInfoConverterFixed : CultureInfoConverter #region Constructors -#if NET35 static Configuration() { + // To be able to resolve UserSettingsGroup of with other framework version AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; +#if NET35 + // To prevent serializing CultureInfo by DisplayName instead of Name typeof(CultureInfo).RegisterTypeConverter(); - } + + // To be able to use HTTP requests with TLS 1.2 security protocol (may not work on Windows XP) + ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072; +#elif NETFRAMEWORK + // To be able to use HTTP requests with TLS 1.2 security protocol (may not work on Windows XP) + ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; #endif + } #endregion @@ -157,14 +165,17 @@ private static void SetInSettings(object value, [CallerMemberName]string propert #region Event handlers -#if NET35 private static Assembly? CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { +#if NETFRAMEWORK if (args.Name.StartsWith("System, Version=", StringComparison.Ordinal)) return typeof(UserSettingsGroup).Assembly; +#elif NETCOREAPP + if (args.Name?.StartsWith("System.Configuration.ConfigurationManager, Version=", StringComparison.Ordinal) == true) + return typeof(UserSettingsGroup).Assembly; +#endif return null; } -#endif #endregion diff --git a/changelog.txt b/changelog.txt index 5b5aefc..58d159d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -15,7 +15,7 @@ + Now panning a zoomed image is possible also by clicking and dragging with the mouse (besides usual scrolling). + Zooming is now possible also with keyboard shortcuts, the Auto Zoom button has now a drop-down part for the additional options. -+ Supporting localization from .resx files with editing and on-the-fly application. ++ Supporting localization from .resx files with editing, downloading and applying without restarting. Note: Right-to-left languages are also supported though with some limitations, especially under Linux/Mono. On Windows some validation tool tips are not aligned right but otherwise the layout is arranged correctly. + Palette visualizer form: OK/Cancel buttons From 25fab88b043508f5eacd168b24c7b98128cdd529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 2 Jun 2021 15:42:19 +0200 Subject: [PATCH 076/211] About window --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 10 +++ KGySoft.Drawing.ImagingTools/Res.cs | 11 +++ .../Forms/ImageVisualizerForm.Designer.cs | 68 ++++++++++++++++++- .../View/Forms/ImageVisualizerForm.cs | 58 ++++++++++------ .../View/Forms/MvvmBaseForm.Designer.cs | 1 + KGySoft.Drawing.ImagingTools/View/Images.cs | 3 +- .../ViewModel/ImageVisualizerViewModel.cs | 22 ++++++ 7 files changed, 152 insertions(+), 21 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index eb47f41..9e0c0a5 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -497,6 +497,16 @@ Dithering may help to preserve more details. {0} file(s) have been downloaded. + + About KGy SOFT Imaging Tools + +Version: v{0} +Author: György Kőszeg +Target Platform: {1} + +You are now using the compiled English resources. +Copyright © {2} KGy SOFT. All rights reserved. + N/A (KGySoft.Drawing.DebuggerVisualizers.dll is missing) diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index c0c1a24..232cd15 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -568,6 +568,17 @@ internal static string InfoColor(int argb, string knownColors, string systemColo /// {0} file(s) have been downloaded. internal static string InfoMessageDownloadComplete(int count) => Get("InfoMessage_DownloadCompleteFormat", count); + /// About KGy SOFT Imaging Tools + /// + /// Version: v{0} + /// Author: György Kőszeg + /// Target Platform: {1} + /// + /// You are now using the compiled English resources. + /// Copyright © {2} KGy SOFT. All rights reserved. + /// + internal static string InfoMessageAbout(Version version, string platform, int year) => Get("InfoMessage_About", version, platform, year); + #endregion #region Installations diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs index 4831e78..30beb81 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs @@ -20,7 +20,6 @@ partial class ImageVisualizerForm private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ImageVisualizerForm)); this.dlgOpen = new System.Windows.Forms.OpenFileDialog(); this.dlgSave = new System.Windows.Forms.SaveFileDialog(); this.timerPlayer = new System.Windows.Forms.Timer(this.components); @@ -57,6 +56,13 @@ private void InitializeComponent() this.btnPrev = new System.Windows.Forms.ToolStripButton(); this.btnNext = new System.Windows.Forms.ToolStripButton(); this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); + this.btnAbout = new System.Windows.Forms.ToolStripSplitButton(); + this.miWebSite = new System.Windows.Forms.ToolStripMenuItem(); + this.miGitHub = new System.Windows.Forms.ToolStripMenuItem(); + this.miMarketplace = new System.Windows.Forms.ToolStripMenuItem(); + this.miSubmitResources = new System.Windows.Forms.ToolStripMenuItem(); + this.miSeparatorAbout = new System.Windows.Forms.ToolStripSeparator(); + this.miAbout = new System.Windows.Forms.ToolStripMenuItem(); this.btnConfiguration = new KGySoft.Drawing.ImagingTools.View.Components.AdvancedToolStripSplitButton(); this.miManageInstallations = new System.Windows.Forms.ToolStripMenuItem(); this.miLanguageSettings = new System.Windows.Forms.ToolStripMenuItem(); @@ -117,6 +123,7 @@ private void InitializeComponent() this.btnPrev, this.btnNext, this.toolStripSeparator4, + this.btnAbout, this.btnConfiguration}); this.tsMenu.Location = new System.Drawing.Point(0, 0); this.tsMenu.Name = "tsMenu"; @@ -344,6 +351,58 @@ private void InitializeComponent() this.toolStripSeparator4.Name = "toolStripSeparator4"; this.toolStripSeparator4.Size = new System.Drawing.Size(6, 25); // + // btnAbout + // + this.btnAbout.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; + this.btnAbout.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnAbout.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.miWebSite, + this.miGitHub, + this.miMarketplace, + this.miSubmitResources, + this.miSeparatorAbout, + this.miAbout}); + this.btnAbout.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnAbout.Name = "btnAbout"; + this.btnAbout.Size = new System.Drawing.Size(16, 22); + this.btnAbout.Text = "btnAbout"; + // + // miWebSite + // + this.miWebSite.Name = "miWebSite"; + this.miWebSite.Size = new System.Drawing.Size(180, 22); + this.miWebSite.Text = "miWebSite"; + // + // miGitHub + // + this.miGitHub.Name = "miGitHub"; + this.miGitHub.Size = new System.Drawing.Size(180, 22); + this.miGitHub.Text = "miGitHub"; + // + // miMarketplace + // + this.miMarketplace.Name = "miMarketplace"; + this.miMarketplace.Size = new System.Drawing.Size(180, 22); + this.miMarketplace.Text = "miMarketplace"; + // + // miSubmitResources + // + this.miSubmitResources.Name = "miSubmitResources"; + this.miSubmitResources.Size = new System.Drawing.Size(180, 22); + this.miSubmitResources.Text = "miSubmitResources"; + // + // miSeparatorAbout + // + this.miSeparatorAbout.Name = "miSeparatorAbout"; + this.miSeparatorAbout.Size = new System.Drawing.Size(177, 6); + // + // miAbout + // + this.miAbout.Name = "miAbout"; + this.miAbout.ShortcutKeys = System.Windows.Forms.Keys.F1; + this.miAbout.Size = new System.Drawing.Size(180, 22); + this.miAbout.Text = "miAbout"; + // // btnConfiguration // this.btnConfiguration.AutoChangeDefaultItem = true; @@ -452,5 +511,12 @@ private void InitializeComponent() private ToolStripMenuItem miManageInstallations; private ToolStripMenuItem miLanguageSettings; protected UserControls.OkCancelButtons okCancelButtons; + private ToolStripSplitButton btnAbout; + private ToolStripMenuItem miWebSite; + private ToolStripMenuItem miGitHub; + private ToolStripMenuItem miMarketplace; + private ToolStripMenuItem miSubmitResources; + private ToolStripSeparator miSeparatorAbout; + private ToolStripMenuItem miAbout; } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index 5552c38..84db4ac 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -91,25 +91,31 @@ protected override void ApplyResources() { base.ApplyResources(); Icon = Properties.Resources.ImagingTools; + btnAntiAlias.Image = Images.SmoothZoom; btnOpen.Image = Images.Open; btnSave.Image = Images.Save; btnClear.Image = Images.Clear; - btnPrev.Image = Images.Prev; - btnNext.Image = Images.Next; - btnColorSettings.Image = Images.Palette; - btnEdit.Image = Images.Edit; - miManageInstallations.Image = Images.Settings; - miLanguageSettings.Image = Images.Language; - btnConfiguration.SetDefaultItem(miManageInstallations); - miShowPalette.Image = Images.Palette; + btnColorSettings.Image = Images.Palette; miBackColorDefault.Image = Images.Check; + miShowPalette.Image = Images.Palette; + + btnEdit.Image = Images.Edit; miRotateLeft.Image = Images.RotateLeft; miRotateRight.Image = Images.RotateRight; miResizeBitmap.Image = Images.Resize; miColorSpace.Image = Images.Quantize; miAdjustColors.Image = Images.Colors; + + btnPrev.Image = Images.Prev; + btnNext.Image = Images.Next; + + miManageInstallations.Image = Images.Settings; + miLanguageSettings.Image = Images.Language; + btnConfiguration.SetDefaultItem(miManageInstallations); + + btnAbout.Image = miAbout.Image = Icons.SystemInformation.ToScaledBitmap(); } protected override void ApplyStringResources() @@ -253,10 +259,6 @@ private void InitCommandBindings() CommandBindings.Add(ViewModel.SetSmoothZoomingCommand, ViewModel.SetSmoothZoomingCommandState) .WithParameter(() => btnAntiAlias.Checked) .AddSource(btnAntiAlias, nameof(btnAntiAlias.CheckedChanged)); - CommandBindings.Add(ViewModel.PrevImageCommand, ViewModel.PrevImageCommandState) - .AddSource(btnPrev, nameof(btnPrev.Click)); - CommandBindings.Add(ViewModel.NextImageCommand, ViewModel.NextImageCommandState) - .AddSource(btnNext, nameof(btnNext.Click)); // File CommandBindings.Add(ViewModel.OpenFileCommand, ViewModel.OpenFileCommandState) @@ -292,22 +294,40 @@ private void InitCommandBindings() CommandBindings.Add(ViewModel.AdjustGammaCommand, ViewModel.EditBitmapCommandState) .AddSource(miGamma, nameof(miGamma.Click)); - // Configuration - CommandBindings.Add(ViewModel.ManageInstallationsCommand) - .AddSource(miManageInstallations, nameof(miManageInstallations.Click)); - CommandBindings.Add(ViewModel.SetLanguageCommand) - .AddSource(miLanguageSettings, nameof(miLanguageSettings.Click)); - - // Compound controls + // Compound images CommandBindings.Add(ViewModel.SetCompoundViewCommand, ViewModel.SetCompoundViewCommandState) .WithParameter(() => btnCompound.Checked) .AddSource(btnCompound, nameof(btnCompound.CheckedChanged)); + CommandBindings.Add(ViewModel.PrevImageCommand, ViewModel.PrevImageCommandState) + .AddSource(btnPrev, nameof(btnPrev.Click)); + CommandBindings.Add(ViewModel.NextImageCommand, ViewModel.NextImageCommandState) + .AddSource(btnNext, nameof(btnNext.Click)); CommandBindings.Add(ViewModel.AdvanceAnimationCommand, ViewModel.AdvanceAnimationCommandState) .AddSource(timerPlayer, nameof(timerPlayer.Tick)); CommandBindings.Add(ViewModel.ViewImagePreviewSizeChangedCommand) .AddSource(imageViewer, nameof(imageViewer.SizeChanged)) .AddSource(imageViewer, nameof(imageViewer.ZoomChanged)); + // Configuration + CommandBindings.Add(ViewModel.ManageInstallationsCommand) + .AddSource(miManageInstallations, nameof(miManageInstallations.Click)); + CommandBindings.Add(ViewModel.SetLanguageCommand) + .AddSource(miLanguageSettings, nameof(miLanguageSettings.Click)); + + // About + CommandBindings.Add(ViewModel.ShowAboutCommand) + .AddSource(btnAbout, nameof(btnAbout.ButtonClick)); + CommandBindings.Add(ViewModel.ShowAboutCommand) + .AddSource(miAbout, nameof(miAbout.Click)); + CommandBindings.Add(ViewModel.VisitWebSiteCommand) + .AddSource(miWebSite, nameof(miWebSite.Click)); + CommandBindings.Add(ViewModel.VisitGitHubCommand) + .AddSource(miGitHub, nameof(miGitHub.Click)); + CommandBindings.Add(ViewModel.VisitMarketplaceCommand) + .AddSource(miMarketplace, nameof(miMarketplace.Click)); + CommandBindings.Add(ViewModel.SubmitResourcesCommand) + .AddSource(miSubmitResources, nameof(miSubmitResources.Click)); + // View commands CommandBindings.Add(OnResizeCommand) .AddSource(this, nameof(Resize)); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs index 92b5839..b5accdc 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs @@ -22,6 +22,7 @@ private void InitializeComponent() this.ClientSize = new System.Drawing.Size(284, 261); this.Name = "MvvmBaseForm"; this.RightToLeftLayout = true; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.ResumeLayout(false); } diff --git a/KGySoft.Drawing.ImagingTools/View/Images.cs b/KGySoft.Drawing.ImagingTools/View/Images.cs index 450c74c..456fe9d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Images.cs +++ b/KGySoft.Drawing.ImagingTools/View/Images.cs @@ -99,7 +99,8 @@ internal static Bitmap ToScaledBitmap(this Icon icon) { if (icon == null) throw new ArgumentNullException(nameof(icon), PublicResources.ArgumentNull); - return icon.ExtractNearestBitmap(referenceSize.Scale(OSUtils.SystemScale), PixelFormat.Format32bppArgb); + using (icon) + return icon.ExtractNearestBitmap(referenceSize.Scale(OSUtils.SystemScale), PixelFormat.Format32bppArgb); } internal static Icon ToScaledIcon(this Icon icon, bool legacyScaling = true) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs index 41f2fd6..2805343 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs @@ -23,6 +23,10 @@ using System.Drawing.Imaging; using System.IO; using System.Linq; +using System.Reflection; +#if !NET35 +using System.Runtime.Versioning; +#endif using System.Text; using KGySoft.ComponentModel; @@ -151,6 +155,11 @@ internal ImageInfo ImageInfo internal ICommand AdjustBrightnessCommand => Get(() => new SimpleCommand(OnAdjustBrightnessCommand)); internal ICommand AdjustContrastCommand => Get(() => new SimpleCommand(OnAdjustContrastCommand)); internal ICommand AdjustGammaCommand => Get(() => new SimpleCommand(OnAdjustGammaCommand)); + internal ICommand ShowAboutCommand => Get(() => new SimpleCommand(OnShowAboutCommand)); + internal ICommand VisitWebSiteCommand => Get(() => new SimpleCommand(() => Process.Start("https://kgysoft.net"))); + internal ICommand VisitGitHubCommand => Get(() => new SimpleCommand(() => Process.Start("https://github.com/koszeggy/KGySoft.Drawing.Tools"))); + internal ICommand VisitMarketplaceCommand => Get(() => new SimpleCommand(() => Process.Start("https://marketplace.visualstudio.com/items?itemName=KGySoft.drawing-debugger-visualizers"))); + internal ICommand SubmitResourcesCommand => Get(() => new SimpleCommand(() => Process.Start("https://github.com/koszeggy/KGySoft.Drawing.Tools/issues/new?assignees=&labels=&template=submit-resources.md&title=%5BRes%5D"))); #endregion @@ -1200,6 +1209,19 @@ private void OnSetLanguageCommand() ShowChildViewCallback?.Invoke(viewModel); } + private void OnShowAboutCommand() + { + Assembly asm = GetType().Assembly; + +#if NET35 + const string frameworkName = ".NET Framework 3.5"; +#else + var attr = (TargetFrameworkAttribute)Attribute.GetCustomAttribute(asm, typeof(TargetFrameworkAttribute)); + string frameworkName = attr.FrameworkDisplayName ?? attr.FrameworkName; +#endif + ShowInfo(Res.InfoMessageAbout(asm.GetName().Version, frameworkName, DateTime.Now.Year)); + } + #endregion #endregion From 5d40a7cc7b9c57cdaf2a9c0a2c7d69ae154364a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 2 Jun 2021 15:42:58 +0200 Subject: [PATCH 077/211] Tidying project file, regenerating resources --- .../KGySoft.Drawing.ImagingTools.csproj | 30 ++++----- .../Properties/Resources.Designer.cs | 64 +++++++++---------- 2 files changed, 46 insertions(+), 48 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj index 3116953..de344ad 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj @@ -8,8 +8,6 @@ bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml true ..\KGySoft.snk - - latest false LICENSE @@ -21,6 +19,7 @@ enable + @@ -30,6 +29,7 @@ + @@ -37,34 +37,32 @@ + + + ResXFileCodeGenerator + Resources.Designer.cs + - PublicResXFileCodeGenerator True True Resources.resx + + + + SettingsSingleFileGenerator + Settings.Designer.cs + True True Settings.settings - - + KGySoft.Drawing.ImagingTools.Messages.resources - - PublicResXFileCodeGenerator - Resources.Designer.cs - - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - diff --git a/KGySoft.Drawing.ImagingTools/Properties/Resources.Designer.cs b/KGySoft.Drawing.ImagingTools/Properties/Resources.Designer.cs index e39ef19..993abb9 100644 --- a/KGySoft.Drawing.ImagingTools/Properties/Resources.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/Properties/Resources.Designer.cs @@ -22,7 +22,7 @@ namespace KGySoft.Drawing.ImagingTools.Properties { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class Resources { + internal class Resources { private static global::System.Resources.ResourceManager resourceMan; @@ -36,7 +36,7 @@ internal Resources() { /// 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 { + internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("KGySoft.Drawing.ImagingTools.Properties.Resources", typeof(Resources).Assembly); @@ -51,7 +51,7 @@ internal Resources() { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture { + internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +63,7 @@ internal Resources() { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Animation { + internal static System.Drawing.Icon Animation { get { object obj = ResourceManager.GetObject("Animation", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -73,7 +73,7 @@ public static System.Drawing.Icon Animation { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Check { + internal static System.Drawing.Icon Check { get { object obj = ResourceManager.GetObject("Check", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -83,7 +83,7 @@ public static System.Drawing.Icon Check { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Clear { + internal static System.Drawing.Icon Clear { get { object obj = ResourceManager.GetObject("Clear", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -93,7 +93,7 @@ public static System.Drawing.Icon Clear { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Colors { + internal static System.Drawing.Icon Colors { get { object obj = ResourceManager.GetObject("Colors", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -103,7 +103,7 @@ public static System.Drawing.Icon Colors { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Compare { + internal static System.Drawing.Icon Compare { get { object obj = ResourceManager.GetObject("Compare", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -113,7 +113,7 @@ public static System.Drawing.Icon Compare { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Crop { + internal static System.Drawing.Icon Crop { get { object obj = ResourceManager.GetObject("Crop", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -123,7 +123,7 @@ public static System.Drawing.Icon Crop { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Edit { + internal static System.Drawing.Icon Edit { get { object obj = ResourceManager.GetObject("Edit", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -133,7 +133,7 @@ public static System.Drawing.Icon Edit { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon HandGrab { + internal static System.Drawing.Icon HandGrab { get { object obj = ResourceManager.GetObject("HandGrab", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -143,7 +143,7 @@ public static System.Drawing.Icon HandGrab { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon HandOpen { + internal static System.Drawing.Icon HandOpen { get { object obj = ResourceManager.GetObject("HandOpen", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -153,7 +153,7 @@ public static System.Drawing.Icon HandOpen { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon HighlightVisibleClip { + internal static System.Drawing.Icon HighlightVisibleClip { get { object obj = ResourceManager.GetObject("HighlightVisibleClip", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -163,7 +163,7 @@ public static System.Drawing.Icon HighlightVisibleClip { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon ImagingTools { + internal static System.Drawing.Icon ImagingTools { get { object obj = ResourceManager.GetObject("ImagingTools", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -173,7 +173,7 @@ public static System.Drawing.Icon ImagingTools { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Language { + internal static System.Drawing.Icon Language { get { object obj = ResourceManager.GetObject("Language", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -183,7 +183,7 @@ public static System.Drawing.Icon Language { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Magnifier { + internal static System.Drawing.Icon Magnifier { get { object obj = ResourceManager.GetObject("Magnifier", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -193,7 +193,7 @@ public static System.Drawing.Icon Magnifier { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Magnifier1 { + internal static System.Drawing.Icon Magnifier1 { get { object obj = ResourceManager.GetObject("Magnifier1", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -203,7 +203,7 @@ public static System.Drawing.Icon Magnifier1 { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon MagnifierMinus { + internal static System.Drawing.Icon MagnifierMinus { get { object obj = ResourceManager.GetObject("MagnifierMinus", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -213,7 +213,7 @@ public static System.Drawing.Icon MagnifierMinus { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon MagnifierPlus { + internal static System.Drawing.Icon MagnifierPlus { get { object obj = ResourceManager.GetObject("MagnifierPlus", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -223,7 +223,7 @@ public static System.Drawing.Icon MagnifierPlus { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon MultiPage { + internal static System.Drawing.Icon MultiPage { get { object obj = ResourceManager.GetObject("MultiPage", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -233,7 +233,7 @@ public static System.Drawing.Icon MultiPage { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon MultiSize { + internal static System.Drawing.Icon MultiSize { get { object obj = ResourceManager.GetObject("MultiSize", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -243,7 +243,7 @@ public static System.Drawing.Icon MultiSize { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Next { + internal static System.Drawing.Icon Next { get { object obj = ResourceManager.GetObject("Next", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -253,7 +253,7 @@ public static System.Drawing.Icon Next { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Open { + internal static System.Drawing.Icon Open { get { object obj = ResourceManager.GetObject("Open", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -263,7 +263,7 @@ public static System.Drawing.Icon Open { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Palette { + internal static System.Drawing.Icon Palette { get { object obj = ResourceManager.GetObject("Palette", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -273,7 +273,7 @@ public static System.Drawing.Icon Palette { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Prev { + internal static System.Drawing.Icon Prev { get { object obj = ResourceManager.GetObject("Prev", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -283,7 +283,7 @@ public static System.Drawing.Icon Prev { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Quantize { + internal static System.Drawing.Icon Quantize { get { object obj = ResourceManager.GetObject("Quantize", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -293,7 +293,7 @@ public static System.Drawing.Icon Quantize { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Resize { + internal static System.Drawing.Icon Resize { get { object obj = ResourceManager.GetObject("Resize", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -303,7 +303,7 @@ public static System.Drawing.Icon Resize { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon RotateLeft { + internal static System.Drawing.Icon RotateLeft { get { object obj = ResourceManager.GetObject("RotateLeft", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -313,7 +313,7 @@ public static System.Drawing.Icon RotateLeft { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon RotateRight { + internal static System.Drawing.Icon RotateRight { get { object obj = ResourceManager.GetObject("RotateRight", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -323,7 +323,7 @@ public static System.Drawing.Icon RotateRight { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Save { + internal static System.Drawing.Icon Save { get { object obj = ResourceManager.GetObject("Save", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -333,7 +333,7 @@ public static System.Drawing.Icon Save { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon Settings { + internal static System.Drawing.Icon Settings { get { object obj = ResourceManager.GetObject("Settings", resourceCulture); return ((System.Drawing.Icon)(obj)); @@ -343,7 +343,7 @@ public static System.Drawing.Icon Settings { /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// - public static System.Drawing.Icon SmoothZoom { + internal static System.Drawing.Icon SmoothZoom { get { object obj = ResourceManager.GetObject("SmoothZoom", resourceCulture); return ((System.Drawing.Icon)(obj)); From 74a9e56814cb41df7afa667a94b3d8dd22c53ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 2 Jun 2021 15:43:42 +0200 Subject: [PATCH 078/211] Fixing AdvancedToolStripSplitButton rendering under .NET 3.5 --- .../View/Components/AdvancedToolStripSplitButton.cs | 2 +- .../View/Controls/AdvancedToolStrip.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs index b601ba8..310b2e8 100644 --- a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs @@ -26,7 +26,7 @@ namespace KGySoft.Drawing.ImagingTools.View.Components { /// - /// A whose button part can be checked. + /// A whose button part can be checked and the default item can automatically be changed. /// // NOTE: The properly scaled arrow and the checked appearance is rendered by ScalingToolStripMenuRenderer, while // the drop-down button size is adjusted in ScalingToolStrip for all ToolStripSplitButtons diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index e274725..63ebd53 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -138,7 +138,7 @@ protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e) { if (e.Item is ToolStripButton { Checked: true, Enabled: true } btn) - ClearButtonBackground(e.Graphics, btn.ContentRectangle, ProfessionalColors.ButtonSelectedGradientMiddle); + ClearButtonBackground(e.Graphics, btn.ContentRectangle, ColorTable.ButtonSelectedGradientMiddle); base.OnRenderButtonBackground(e); } @@ -160,18 +160,18 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr { rect.Inflate(-1, -1); if (btn.ButtonPressed) - ClearButtonBackground(e.Graphics, rect, ProfessionalColors.ButtonPressedHighlight); + ClearButtonBackground(e.Graphics, rect, ColorTable.ButtonPressedHighlight); else if (btn.Selected) - ClearButtonBackground(e.Graphics, rect, btn.Checked ? ProfessionalColors.ButtonPressedHighlight : ProfessionalColors.ButtonSelectedGradientMiddle); + ClearButtonBackground(e.Graphics, rect, btn.Checked ? ColorTable.ButtonPressedHighlight : ColorTable.ButtonSelectedGradientMiddle); else if (btn.Checked) - ClearButtonBackground(e.Graphics, rect, ProfessionalColors.ButtonSelectedGradientMiddle); + ClearButtonBackground(e.Graphics, rect, ColorTable.ButtonSelectedGradientMiddle); rect.Inflate(1, 1); } // drawing border (maybe again, because it can be overridden by background) if (btn.Checked || !btn.DropDownButtonPressed && (btn.ButtonPressed || btn.ButtonSelected)) { - using (Pen pen = new Pen(ProfessionalColors.ButtonSelectedBorder)) + using (Pen pen = new Pen(ColorTable.ButtonSelectedBorder)) e.Graphics.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height - 1); } } From c1066adcd13ed8ae4829615d55fc1c5b96062fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 2 Jun 2021 16:16:29 +0200 Subject: [PATCH 079/211] Adding resources for missing control names --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 9e0c0a5..dce08f7 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -811,6 +811,9 @@ Newly generated resources will contain English texts prefixed with "[T]". You ca &Edit Resources... + + &Download Resources... + Resource File @@ -832,4 +835,46 @@ Newly generated resources will contain English texts prefixed with "[T]". You ca Translated Text + + Downloading Resources + + + Selected + + + Language + + + Author + + + Version + + + Imaging Tools Version + + + Description + + + Download + + + About + + + Visit Web Site... + + + Visit GitHub Project Site... + + + Visit VisualStudio Marketplace... + + + Submit Language Resource Files... + + + About... + \ No newline at end of file From e00cb566b2e08ce7585623f0a94fa8fd0c9d8e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 2 Jun 2021 16:16:50 +0200 Subject: [PATCH 080/211] Fixing appearance --- .../View/Forms/DownloadResourcesForm.Designer.cs | 1 - .../View/Forms/DownloadResourcesForm.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs index 41ce5ac..92bed72 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs @@ -48,7 +48,6 @@ private void InitializeComponent() this.gridDownloadableResources.AllowUserToAddRows = false; this.gridDownloadableResources.AllowUserToDeleteRows = false; this.gridDownloadableResources.AutoGenerateColumns = false; - this.gridDownloadableResources.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.gridDownloadableResources.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.colSelected, this.colLanguage, diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs index 19e283a..e2fb2bf 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs @@ -33,7 +33,7 @@ internal partial class DownloadResourcesForm : MvvmBaseForm Date: Wed, 2 Jun 2021 16:28:55 +0200 Subject: [PATCH 081/211] Download: Fixing analysis warning and dialog appearance --- .../View/Forms/DownloadResourcesForm.cs | 2 +- .../ViewModel/DownloadResourcesViewModel.cs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs index e2fb2bf..3373798 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs @@ -33,7 +33,7 @@ internal partial class DownloadResourcesForm : MvvmBaseForm CloseViewCallback?.Invoke()); + TryInvokeSync(() => + { + ShowError(Res.ErrorMessageCouldNotAccessOnlineResources(e.Message)); + CloseViewCallback?.Invoke(); + }); } finally { From 83b0c8122b2dc24c77989f99b6cf821f15a511ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 2 Jun 2021 17:09:15 +0200 Subject: [PATCH 082/211] Fixing possible wrong size info for multi-res images --- .../ViewModel/ImageVisualizerViewModel.cs | 35 ++++++++++++------- changelog.txt | 1 + 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs index 2805343..3b4a6a3 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs @@ -67,7 +67,7 @@ internal class ImageVisualizerViewModel : ViewModelBase, IViewModel, private int currentFrame = -1; private bool isOpenFilterUpToDate; private Size currentResolution; - private bool deferSettingCompoundStateImage; + private bool deferUpdateInfo; private string? notificationId; #endregion @@ -243,8 +243,13 @@ private static bool TryLoadCustom(MemoryStream stream, [MaybeNullWhen(false)]out internal override void ViewLoaded() { InitAutoZoom(true); - if (deferSettingCompoundStateImage && SetCompoundViewCommandState.GetValueOrDefault(stateVisible)) - SetCompoundViewCommandStateImage(); + if (deferUpdateInfo) + { + if (SetCompoundViewCommandState.GetValueOrDefault(stateVisible)) + SetCompoundViewCommandStateImage(); + UpdateIfMultiResImage(); + } + base.ViewLoaded(); } @@ -495,7 +500,7 @@ private bool IsSingleImageShown() => imageInfo.Type != ImageInfoType.None && !im private void SetCompoundViewCommandStateImage() { Func? callback = GetCompoundViewIconCallback; - deferSettingCompoundStateImage = callback == null; + deferUpdateInfo |= callback == null; if (callback != null) SetCompoundViewCommandState[stateImage] = callback.Invoke(imageInfo.Type); } @@ -723,7 +728,11 @@ private void UpdateMultiResImage() if (!imageInfo.IsMultiRes || currentFrame != -1) return; Size origSize = currentResolution; - Size clientSize = GetImagePreviewSizeCallback?.Invoke() ?? default; + Func? callback = GetImagePreviewSizeCallback; + deferUpdateInfo |= callback == null; + if (callback == null) + return; + Size clientSize = callback.Invoke(); int desiredSize = Math.Min(clientSize.Width, clientSize.Height); if (desiredSize < 1 && !origSize.IsEmpty) return; @@ -740,6 +749,14 @@ private void UpdateMultiResImage() PreviewImage = desiredImage.Image; } + private void UpdateIfMultiResImage() + { + if (!imageInfo.IsMultiRes || currentFrame != -1) + return; + UpdateMultiResImage(); + UpdateInfo(); + } + private void SaveIcon(string fileName) { using (Stream stream = File.Create(fileName)) @@ -1065,13 +1082,7 @@ ImageInfo IViewModel.GetEditedModel() private void OnSetSmoothZoomingCommand(bool newValue) => SmoothZooming = newValue; - private void OnViewImagePreviewSizeChangedCommand() - { - if (!imageInfo.IsMultiRes || currentFrame != -1) - return; - UpdateMultiResImage(); - UpdateInfo(); - } + private void OnViewImagePreviewSizeChangedCommand() => UpdateIfMultiResImage(); private void OnOpenFileCommand() => OpenFile(); diff --git a/changelog.txt b/changelog.txt index 58d159d..52ddf44 100644 --- a/changelog.txt +++ b/changelog.txt @@ -48,6 +48,7 @@ - KGySoft.Drawing.DebuggerVisualizers.dll ========================================= - Fixing a possible IndexOutOfRangeException when debugging an animated GIF. +- Fixing possible incorrect initially displayed size info when debugging a multi-resolution icon or bitmap. * Improved performance and reduced memory consumption when serializing and deserializing debugged objects. * KGySoft.Drawing.DebuggerVisualizers.Package.dll From de0f9026b01d0a96d94c45b6ad63b62aca257503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 3 Jun 2021 15:38:05 +0200 Subject: [PATCH 083/211] Fixing possible argument null exception in .NET 3.5 where version is not always available --- .../ViewModel/ManageInstallationsViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs index 31b3a5e..c6c7c89 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs @@ -211,7 +211,7 @@ private void OnSelectFolderCommand() private void OnInstallCommand() { - if (currentStatus.Installed && !Confirm(Res.ConfirmMessageOverwriteInstallation, InstallationManager.AvailableVersion.Version > currentStatus.Version)) + if (currentStatus.Installed && !Confirm(Res.ConfirmMessageOverwriteInstallation, currentStatus.Version != null && InstallationManager.AvailableVersion.Version > currentStatus.Version)) return; #if NETCOREAPP if (!Confirm(Res.ConfirmMessageNetCoreVersion, false)) From e01be2a11e4c4526f28f299fbfd098605ca8df71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 3 Jun 2021 18:55:57 +0200 Subject: [PATCH 084/211] Fixing menu item scaling in Linux/Mono --- .../View/Components/ZoomSplitButton.cs | 9 ++++ .../View/Controls/AdvancedToolStrip.cs | 42 ++++++++++++++++--- .../View/_Extensions/ControlExtensions.cs | 3 +- changelog.txt | 1 + 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs index 6b49825..d074f9e 100644 --- a/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs @@ -92,6 +92,15 @@ public ZoomSplitButton() items.AddRange(new ToolStripItem[] { AutoZoomMenuItem, IncreaseZoomMenuItem, DecreaseZoomMenuItem, ResetZoomMenuItem }); } + protected override void OnParentChanged(ToolStrip? oldParent, ToolStrip? newParent) + { + base.OnParentChanged(oldParent, newParent); + + // Linux/Mono: without this the new parent's renderer will not be applied to the drop down menu strip + if (!OSUtils.IsWindows && newParent != null) + AutoZoomMenuItem.Owner.Renderer = newParent.Renderer; + } + #endregion #region Methods diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 63ebd53..5afb1de 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -21,6 +21,7 @@ using System.Drawing.Drawing2D; using System.Windows.Forms; +using KGySoft.CoreLibraries; using KGySoft.Drawing.ImagingTools.View.Components; using KGySoft.Drawing.ImagingTools.WinApi; using KGySoft.Reflection; @@ -53,7 +54,7 @@ private class ScalingToolStripMenuRenderer : ToolStripProfessionalRenderer #region Static Methods - private static void ClearButtonBackground(Graphics g, Rectangle rect, Color color) + private static void ClearImageBackground(Graphics g, Rectangle rect, Color color) { GraphicsState state = g.Save(); rect.Inflate(1, 1); @@ -138,7 +139,7 @@ protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e) { if (e.Item is ToolStripButton { Checked: true, Enabled: true } btn) - ClearButtonBackground(e.Graphics, btn.ContentRectangle, ColorTable.ButtonSelectedGradientMiddle); + ClearImageBackground(e.Graphics, btn.ContentRectangle, ColorTable.ButtonSelectedGradientMiddle); base.OnRenderButtonBackground(e); } @@ -160,11 +161,11 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr { rect.Inflate(-1, -1); if (btn.ButtonPressed) - ClearButtonBackground(e.Graphics, rect, ColorTable.ButtonPressedHighlight); + ClearImageBackground(e.Graphics, rect, ColorTable.ButtonPressedHighlight); else if (btn.Selected) - ClearButtonBackground(e.Graphics, rect, btn.Checked ? ColorTable.ButtonPressedHighlight : ColorTable.ButtonSelectedGradientMiddle); + ClearImageBackground(e.Graphics, rect, btn.Checked ? ColorTable.ButtonPressedHighlight : ColorTable.ButtonSelectedGradientMiddle); else if (btn.Checked) - ClearButtonBackground(e.Graphics, rect, ColorTable.ButtonSelectedGradientMiddle); + ClearImageBackground(e.Graphics, rect, ColorTable.ButtonSelectedGradientMiddle); rect.Inflate(1, 1); } @@ -176,6 +177,27 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr } } + protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) + { + // Fixing image scaling in menu items on Linux/Mono + if (!OSUtils.IsWindows && e.Item is ToolStripMenuItem mi) + { + Rectangle rect = e.ImageRectangle; + rect.Size = e.Item.Owner.ScaleSize(referenceSize); + e = new ToolStripItemImageRenderEventArgs(e.Graphics, e.Item, e.Image, rect); + + // Windows paints this in base but Linux/Mono does not + if (mi.Checked) + { + ClearImageBackground(e.Graphics, rect, mi.Selected ? ColorTable.ButtonPressedHighlight : ColorTable.ButtonSelectedGradientMiddle); + using (Pen pen = new Pen(ColorTable.ButtonSelectedBorder)) + e.Graphics.DrawRectangle(pen, rect.X - 1, rect.Y - 1, rect.Width + 1, rect.Height + 1); + } + } + + base.OnRenderItemImage(e); + } + #endregion #endregion @@ -208,7 +230,6 @@ public AdvancedToolStrip() { ImageScalingSize = Size.Round(this.ScaleSize(referenceSize)); Renderer = new ScalingToolStripMenuRenderer(); - toolTip = Reflector.TryGetProperty(this, nameof(ToolTip), out object? result) ? (ToolTip)result! : Reflector.TryGetField(this, "tooltip_window", out result) ? (ToolTip)result! : null; @@ -251,6 +272,15 @@ protected override void OnItemAdded(ToolStripItemEventArgs e) base.OnItemAdded(e); } + protected override void OnSizeChanged(EventArgs e) + { + base.OnSizeChanged(e); + + // Preventing double scaling in Linux/Mono + if (!OSUtils.IsWindows && Dock.In(DockStyle.Top, DockStyle.Bottom)) + Height = this.ScaleHeight(25); + } + protected override void OnDockChanged(EventArgs e) { base.OnDockChanged(e); diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs index ff27f6b..000e99c 100644 --- a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs @@ -19,7 +19,7 @@ using System; using System.Drawing; using System.Windows.Forms; -using KGySoft.Drawing.ImagingTools.View.Controls; + using KGySoft.Reflection; #endregion @@ -58,6 +58,7 @@ internal static PointF GetScale(this Control control) internal static Size ScaleSize(this Control control, Size size) => size.Scale(control.GetScale()); internal static int ScaleWidth(this Control control, int width) => width.Scale(control.GetScale().X); + internal static int ScaleHeight(this Control control, int height) => height.Scale(control.GetScale().Y); /// /// Applies fixed string resources (which do not change unless language is changed) to a control. diff --git a/changelog.txt b/changelog.txt index 52ddf44..9ccc35c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -23,6 +23,7 @@ + Image visualizer form: OK/Cancel buttons * Manual zooming is enabled even if Auto Zoom is on; this turns off Auto Zoom automatically. * Turning smoothing of zoomed images on/off affects also shrunk images (previously affected enlarged images only). +- Fixing scaling of menu item images under Linux/Mono when using high DPI - Color Count: Result was not always shown (and progress bar was not removed) if the operation ended very quickly. - Resize: - Preventing that invalid sizes replace current text to 0 From 70afeddf0a225991db946de3e3ed5b0b3a363e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 3 Jun 2021 20:48:31 +0200 Subject: [PATCH 085/211] Extracting an AdvancedPropertyGrid control from EditResources and using it everywhere --- .../View/Controls/AdvancedPropertyGrid.cs | 139 ++++++++++++++++++ .../Forms/DownloadResourcesForm.Designer.cs | 4 +- .../View/Forms/EditResourcesForm.Designer.cs | 33 ++--- .../View/Forms/EditResourcesForm.cs | 12 -- 4 files changed, 151 insertions(+), 37 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/View/Controls/AdvancedPropertyGrid.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedPropertyGrid.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedPropertyGrid.cs new file mode 100644 index 0000000..b1d5414 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedPropertyGrid.cs @@ -0,0 +1,139 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: AdvancedDataGridView.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Windows.Forms; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View.Controls +{ + /// + /// Just providing some default style with a few fixed issues + /// + internal class AdvancedDataGridView : DataGridView + { + #region Fields + + private readonly DataGridViewCellStyle defaultDefaultCellStyle; + private readonly DataGridViewCellStyle defaultColumnHeadersDefaultCellStyle; + private readonly DataGridViewCellStyle defaultAlternatingRowsDefaultCellStyle; + + #endregion + + #region Properties + + // these are reintroduced just for the ShouldSerialize... methods + [AmbientValue(null)] + [AllowNull] + public new DataGridViewCellStyle DefaultCellStyle + { + get => base.DefaultCellStyle; + set => base.DefaultCellStyle = value; + } + + [AmbientValue(null)] + [AllowNull] + public new DataGridViewCellStyle ColumnHeadersDefaultCellStyle + { + get => base.ColumnHeadersDefaultCellStyle; + set => base.ColumnHeadersDefaultCellStyle = value; + } + + [AmbientValue(null)] + [AllowNull] + public new DataGridViewCellStyle AlternatingRowsDefaultCellStyle + { + get => base.AlternatingRowsDefaultCellStyle; + set => base.AlternatingRowsDefaultCellStyle = value; + } + + #endregion + + #region Constructors + + public AdvancedDataGridView() + { + DefaultCellStyle = defaultDefaultCellStyle = new DataGridViewCellStyle(DefaultCellStyle) + { + // Base default uses Window back color with ControlText fore color. Most cases it's not an issue unless Window/Control colors are close to inverted. + BackColor = SystemColors.Window, + ForeColor = SystemColors.WindowText, + }; + + ColumnHeadersDefaultCellStyle = defaultColumnHeadersDefaultCellStyle = new DataGridViewCellStyle + { + // Base default uses Control back color with WindowText fore color. Most cases it's not an issue unless Window/Control colors are close to inverted. + BackColor = SystemColors.Control, + ForeColor = SystemColors.ControlText, + }; + + AlternatingRowsDefaultCellStyle = defaultAlternatingRowsDefaultCellStyle = new DataGridViewCellStyle + { + BackColor = SystemColors.ControlLight, + ForeColor = SystemColors.ControlText, + }; + } + + #endregion + + #region Methods + + #region Protected Methods + + protected override void OnParentChanged(EventArgs e) + { + base.OnParentChanged(e); + AdjustAlternatingRowsColors(); + } + + protected override void OnSystemColorsChanged(EventArgs e) + { + base.OnSystemColorsChanged(e); + AdjustAlternatingRowsColors(); + AlternatingRowsDefaultCellStyle = SystemInformation.HighContrast + ? null + : new DataGridViewCellStyle { BackColor = SystemColors.ControlLight, ForeColor = SystemColors.ControlText }; + } + + #endregion + + #region Private Methods + + private void AdjustAlternatingRowsColors() + { + if (!Equals(AlternatingRowsDefaultCellStyle, defaultAlternatingRowsDefaultCellStyle)) + return; + + AlternatingRowsDefaultCellStyle = SystemInformation.HighContrast + ? null + : defaultAlternatingRowsDefaultCellStyle; + } + + private bool ShouldSerializeAlternatingRowsDefaultCellStyle() => !Equals(AlternatingRowsDefaultCellStyle, defaultAlternatingRowsDefaultCellStyle); + private bool ShouldSerializeColumnHeadersDefaultCellStyle() => !Equals(ColumnHeadersDefaultCellStyle, defaultColumnHeadersDefaultCellStyle); + private bool ShouldSerializeDefaultCellStyle() => !Equals(DefaultCellStyle, defaultDefaultCellStyle); + + #endregion + + #endregion + } +} diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs index 92bed72..050e341 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs @@ -19,7 +19,7 @@ private void InitializeComponent() this.components = new System.ComponentModel.Container(); this.bindingSource = new System.Windows.Forms.BindingSource(this.components); this.okCancelButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); - this.gridDownloadableResources = new System.Windows.Forms.DataGridView(); + this.gridDownloadableResources = new Controls.AdvancedDataGridView(); this.colSelected = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.colLanguage = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colAuthor = new System.Windows.Forms.DataGridViewTextBoxColumn(); @@ -140,7 +140,7 @@ private void InitializeComponent() #endregion private Controls.DownloadProgressStatusStrip progress; - private System.Windows.Forms.DataGridView gridDownloadableResources; + private Controls.AdvancedDataGridView gridDownloadableResources; private System.Windows.Forms.BindingSource bindingSource; private UserControls.OkCancelButtons okCancelButtons; private System.Windows.Forms.DataGridViewCheckBoxColumn colSelected; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index cfad2e5..a063ba6 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -31,10 +31,8 @@ private void InitializeComponent() { this.components = new System.ComponentModel.Container(); System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle(); this.gbResourceEntries = new System.Windows.Forms.GroupBox(); - this.gridResources = new System.Windows.Forms.DataGridView(); + this.gridResources = new KGySoft.Drawing.ImagingTools.View.Controls.AdvancedDataGridView(); this.colResourceKey = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colOriginalText = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colTranslatedText = new System.Windows.Forms.DataGridViewTextBoxColumn(); @@ -72,32 +70,21 @@ private void InitializeComponent() // this.gridResources.AllowUserToAddRows = false; this.gridResources.AllowUserToDeleteRows = false; - dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.ControlLight; - dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.ControlText; - this.gridResources.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1; this.gridResources.AutoGenerateColumns = false; - dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; - dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Control; - dataGridViewCellStyle2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); - dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.ControlText; - dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight; - dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText; - dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.gridResources.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2; this.gridResources.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.gridResources.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.colResourceKey, this.colOriginalText, this.colTranslatedText}); this.gridResources.DataSource = this.bindingSource; - dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; - dataGridViewCellStyle3.BackColor = System.Drawing.SystemColors.Window; - dataGridViewCellStyle3.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); - dataGridViewCellStyle3.ForeColor = System.Drawing.SystemColors.WindowText; - dataGridViewCellStyle3.SelectionBackColor = System.Drawing.SystemColors.Highlight; - dataGridViewCellStyle3.SelectionForeColor = System.Drawing.SystemColors.HighlightText; - dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.gridResources.DefaultCellStyle = dataGridViewCellStyle3; + dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; + dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Window; + dataGridViewCellStyle1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.WindowText; + dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight; + dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText; + dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True; + this.gridResources.DefaultCellStyle = dataGridViewCellStyle1; this.gridResources.Dock = System.Windows.Forms.DockStyle.Fill; this.gridResources.Location = new System.Drawing.Point(3, 16); this.gridResources.Name = "gridResources"; @@ -264,7 +251,7 @@ private void InitializeComponent() private UserControls.OkCancelButtons okCancelApplyButtons; private System.Windows.Forms.GroupBox gbResourceEntries; - private System.Windows.Forms.DataGridView gridResources; + private Controls.AdvancedDataGridView gridResources; private System.Windows.Forms.TableLayoutPanel pnlEditResourceEntry; private System.Windows.Forms.GroupBox gbOriginalText; private System.Windows.Forms.GroupBox gbTranslatedText; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs index adbcddc..5852481 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs @@ -16,9 +16,7 @@ #region Usings -using System; using System.Collections.Generic; -using System.Drawing; using System.Windows.Forms; using KGySoft.Drawing.ImagingTools.Model; @@ -38,8 +36,6 @@ internal EditResourcesForm(EditResourcesViewModel viewModel) : base(viewModel) { // Note: Not setting Accept/CancelButton because they would be very annoying during the editing InitializeComponent(); - if (SystemInformation.HighContrast) - gridResources.AlternatingRowsDefaultCellStyle = null; cmbResourceFiles.ValueMember = nameof(KeyValuePair.Key); cmbResourceFiles.DisplayMember = nameof(KeyValuePair.Value); } @@ -74,14 +70,6 @@ protected override void ApplyViewModel() base.ApplyViewModel(); } - protected override void OnSystemColorsChanged(EventArgs e) - { - base.OnSystemColorsChanged(e); - gridResources.AlternatingRowsDefaultCellStyle = SystemInformation.HighContrast - ? null - : new DataGridViewCellStyle { BackColor = SystemColors.ControlLight, ForeColor = SystemColors.ControlText }; - } - #endregion #region Private Methods From a590c9e53cc316bab358e282edda9d85d2b84a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 3 Jun 2021 21:29:21 +0200 Subject: [PATCH 086/211] Download: confirm on version mismatch --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 4 +++ KGySoft.Drawing.ImagingTools/Res.cs | 4 +++ .../ViewModel/DownloadResourcesViewModel.cs | 10 ++++++ .../_Extensions/VersionExtensions.cs | 36 +++++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 KGySoft.Drawing.ImagingTools/_Extensions/VersionExtensions.cs diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index dce08f7..142c841 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -455,6 +455,10 @@ Do you want to overwrite them? There are unsaved modifications. Are sure to discard the changes? + + One or more selected items are for a different Imaging Tools version. +Are you sure you want to continue? + The palette contains no colors. Click OK to exit. diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 232cd15..e8a6861 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -205,6 +205,10 @@ internal static class Res /// There are unsaved modifications. Are sure to discard the changes? internal static string ConfirmMessageDiscardChanges => Get("ConfirmMessage_DiscardChanges"); + /// One or more selected items are for a different Imaging Tools version. + /// Are you sure you want to continue? + internal static string ConfirmMessageResourceVersionMismatch => Get("ConfirmMessage_ResourceVersionMismatch"); + /// The palette contains no colors. Click OK to exit. internal static string InfoMessagePaletteEmpty => Get("InfoMessage_PaletteEmpty"); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index a43aa5c..29e7d3d 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -351,12 +351,22 @@ private void OnDownloadCommand() { var toDownload = new List(); var existingFiles = new List(); + Version toolVersion = GetType().Assembly.GetName().Version.Normalize(); + bool ignoreVersionMismatch = false; foreach (DownloadableResourceItem item in Items!) { if (!item.Selected) continue; + if (!ignoreVersionMismatch && item.Info.ImagingToolsVersion.Normalize() != toolVersion) + { + if (Confirm(Res.ConfirmMessageResourceVersionMismatch, false)) + ignoreVersionMismatch = true; + else + return; + } + LocalizationInfo info = item.Info; foreach (ResourceLibraries lib in info.ResourceSets.GetFlags(false)) { diff --git a/KGySoft.Drawing.ImagingTools/_Extensions/VersionExtensions.cs b/KGySoft.Drawing.ImagingTools/_Extensions/VersionExtensions.cs new file mode 100644 index 0000000..f11b9ef --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/_Extensions/VersionExtensions.cs @@ -0,0 +1,36 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: VersionExtensions.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; + +#endregion + +namespace KGySoft.Drawing.ImagingTools +{ + internal static class VersionExtensions + { + #region Methods + + internal static Version Normalize(this Version version) + => version.Revision >= 0 ? version + : version.Build >= 0 ? new Version(version.Major, version.Minor, version.Build, 0) + : new Version(version.Major, version.Minor, version.Build, version.Revision); + + #endregion + } +} \ No newline at end of file From 126d850a155b95dad4643e00644496010a8bda2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 5 Jun 2021 12:14:11 +0200 Subject: [PATCH 087/211] Removing Version from LocalizationInfo --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 3 -- .../Model/LocalizationInfo.cs | 1 - .../Forms/DownloadResourcesForm.Designer.cs | 35 +++++++------------ .../ViewModel/DownloadResourcesViewModel.cs | 2 +- .../ViewModel/DownloadableResourceItem.cs | 1 - 5 files changed, 13 insertions(+), 29 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 142c841..170889e 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -851,9 +851,6 @@ Newly generated resources will contain English texts prefixed with "[T]". You ca Author - - Version - Imaging Tools Version diff --git a/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs b/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs index 8704266..cbe1ac9 100644 --- a/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs +++ b/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs @@ -30,7 +30,6 @@ internal class LocalizationInfo public CultureInfo Language { get; set; } = default!; public string? Description { get; set; } public ResourceLibraries ResourceSets { get; set; } = default!; - public Version Version { get; set; } = default!; public Version ImagingToolsVersion { get; set; } = default!; public string? Author { get; set; } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs index 050e341..7f0862a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs @@ -19,14 +19,13 @@ private void InitializeComponent() this.components = new System.ComponentModel.Container(); this.bindingSource = new System.Windows.Forms.BindingSource(this.components); this.okCancelButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); - this.gridDownloadableResources = new Controls.AdvancedDataGridView(); + this.gridDownloadableResources = new KGySoft.Drawing.ImagingTools.View.Controls.AdvancedDataGridView(); + this.progress = new KGySoft.Drawing.ImagingTools.View.Controls.DownloadProgressStatusStrip(); this.colSelected = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.colLanguage = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colAuthor = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colVersion = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colImagingToolsVersion = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colDescription = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.progress = new KGySoft.Drawing.ImagingTools.View.Controls.DownloadProgressStatusStrip(); ((System.ComponentModel.ISupportInitialize)(this.bindingSource)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.gridDownloadableResources)).BeginInit(); this.SuspendLayout(); @@ -52,7 +51,6 @@ private void InitializeComponent() this.colSelected, this.colLanguage, this.colAuthor, - this.colVersion, this.colImagingToolsVersion, this.colDescription}); this.gridDownloadableResources.DataSource = this.bindingSource; @@ -62,6 +60,16 @@ private void InitializeComponent() this.gridDownloadableResources.Size = new System.Drawing.Size(358, 163); this.gridDownloadableResources.TabIndex = 0; // + // progress + // + this.progress.BackColor = System.Drawing.Color.Transparent; + this.progress.Location = new System.Drawing.Point(3, 201); + this.progress.Name = "progress"; + this.progress.Size = new System.Drawing.Size(358, 22); + this.progress.SizingGrip = false; + this.progress.TabIndex = 2; + this.progress.Text = "drawingProgressStatusStrip1"; + // // colSelected // this.colSelected.DataPropertyName = "Selected"; @@ -83,14 +91,6 @@ private void InitializeComponent() this.colAuthor.Name = "colAuthor"; this.colAuthor.ReadOnly = true; // - // colVersion - // - this.colVersion.DataPropertyName = "Version"; - this.colVersion.HeaderText = "colVersion"; - this.colVersion.Name = "colVersion"; - this.colVersion.ReadOnly = true; - this.colVersion.Width = 60; - // // colImagingToolsVersion // this.colImagingToolsVersion.DataPropertyName = "ImagingToolsVersion"; @@ -107,16 +107,6 @@ private void InitializeComponent() this.colDescription.ReadOnly = true; this.colDescription.Width = 120; // - // progress - // - this.progress.BackColor = System.Drawing.Color.Transparent; - this.progress.Location = new System.Drawing.Point(3, 201); - this.progress.Name = "progress"; - this.progress.Size = new System.Drawing.Size(358, 22); - this.progress.SizingGrip = false; - this.progress.TabIndex = 2; - this.progress.Text = "drawingProgressStatusStrip1"; - // // DownloadResourcesForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -146,7 +136,6 @@ private void InitializeComponent() private System.Windows.Forms.DataGridViewCheckBoxColumn colSelected; private System.Windows.Forms.DataGridViewTextBoxColumn colLanguage; private System.Windows.Forms.DataGridViewTextBoxColumn colAuthor; - private System.Windows.Forms.DataGridViewTextBoxColumn colVersion; private System.Windows.Forms.DataGridViewTextBoxColumn colImagingToolsVersion; private System.Windows.Forms.DataGridViewTextBoxColumn colDescription; } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index 29e7d3d..d499a82 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -92,7 +92,7 @@ public DownloadInfo(LocalizationInfo info, ResourceLibraries library) FileName = Equals(info.Language, Res.DefaultLanguage) ? ResHelper.GetBaseName(library) + ".resx" : $"{ResHelper.GetBaseName(library)}.{info.Language.Name}.resx"; - remotePath = $"{info.Language.Name}_{info.Author}_{info.Version}"; + remotePath = $"{info.Language.Name}_{info.Author}_{info.ImagingToolsVersion}"; } #endregion diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs index eea6b84..2422d7c 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs @@ -32,7 +32,6 @@ internal class DownloadableResourceItem : ObservableObjectBase public bool Selected { get => Get(); set => Set(value); } public string Language => $"{Info.Language.EnglishName} ({Info.Language.NativeName})"; public string? Author => Info.Author; - public string Version => Info.Version.ToString(); public string ImagingToolsVersion => Info.ImagingToolsVersion.ToString(); public string? Description => Info.Description; From 55d98e8e02daa84f804282236e1e7e50b50c5653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 5 Jun 2021 12:26:40 +0200 Subject: [PATCH 088/211] Allowing Download only if there is at least 1 selected item --- .../ViewModel/DownloadResourcesViewModel.cs | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index d499a82..7453121 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -18,8 +18,10 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Globalization; using System.IO; +using System.Linq; using System.Net; using System.Text; using System.Threading; @@ -57,8 +59,8 @@ private sealed class DownloadResourcesTask : DownloadTask { #region Fields - internal List Files; - internal bool Owerwrite; + internal List Files = default!; + internal bool Overwrite; #endregion } @@ -194,13 +196,14 @@ private void DoDownloadManifest(object state) using var reader = XmlReader.Create(new StreamReader(new MemoryStream(data), Encoding.UTF8)); reader.ReadStartElement("manifest"); - var items = new List(); - XmlSerializer.DeserializeContent(reader, items); + var itemsList = new List(); + XmlSerializer.DeserializeContent(reader, itemsList); TryInvokeSync(() => { - Items = new DownloadableResourceItemCollection(items); - DownloadCommandState.Enabled = true; + var items = new DownloadableResourceItemCollection(itemsList); + items.ListChanged += Items_ListChanged; + Items = items; IsProcessing = false; }); } @@ -223,7 +226,7 @@ private void BeginDownloadResources(List toDownload, bool overwrit { DownloadCommandState.Enabled = false; IsProcessing = true; - activeTask = new DownloadResourcesTask { Files = toDownload, Owerwrite = overwrite }; + activeTask = new DownloadResourcesTask { Files = toDownload, Overwrite = overwrite }; ThreadPool.QueueUserWorkItem(DoDownloadResources, activeTask); } @@ -245,7 +248,7 @@ private void DoDownloadResources(object state) if (task.IsCanceled) return; - if (!task.Owerwrite && File.Exists(downloadInfo.LocalPath)) + if (!task.Overwrite && File.Exists(downloadInfo.LocalPath)) { IncrementProgress(3); continue; @@ -339,6 +342,19 @@ private void IncrementProgress(int value = 1) #endregion + #region Event Handlers + + private void Items_ListChanged(object sender, ListChangedEventArgs e) + { + var items = (DownloadableResourceItemCollection)sender; + if (e.ListChangedType != ListChangedType.ItemChanged || e.PropertyDescriptor?.Name != nameof(DownloadableResourceItem.Selected)) + return; + + DownloadCommandState.Enabled = items[e.NewIndex].Selected || items.Any(i => i.Selected); + } + + #endregion + #region Command Handlers private void OnCancelCommand() From a552cccf82d85e3b084057927e34c2b8857b46d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 5 Jun 2021 12:46:22 +0200 Subject: [PATCH 089/211] Excluding the invariant culture from the languages list --- .../ViewModel/LanguageSettingsViewModel.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index 36b59fc..9face79 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -33,7 +33,7 @@ internal class LanguageSettingsViewModel : ViewModelBase { #region Fields - private CultureInfo[]? neutralLanguages; + private List? neutralLanguages; private HashSet? availableResXLanguages; private List? selectableLanguages; private CultureInfo? dirtyCulture; @@ -62,7 +62,26 @@ internal class LanguageSettingsViewModel : ViewModelBase #region Private Properties - private CultureInfo[] NeutralLanguages => neutralLanguages ??= CultureInfo.GetCultures(CultureTypes.NeutralCultures); + private List NeutralLanguages + { + get + { + if (neutralLanguages == null) + { + CultureInfo[] result = CultureInfo.GetCultures(CultureTypes.NeutralCultures); + neutralLanguages = new List(result.Length - 1); + foreach (CultureInfo ci in result) + { + if (Equals(ci, CultureInfo.InvariantCulture)) + continue; + neutralLanguages.Add(ci); + } + } + + return neutralLanguages; + } + } + private HashSet AvailableLanguages => availableResXLanguages ??= ResHelper.GetAvailableLanguages(); private List SelectableLanguages From f916ef5f7dcd81d5dead45a78a39e0d099d8cbf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 5 Jun 2021 16:21:42 +0200 Subject: [PATCH 090/211] Download: Handling if a culture is not supported on current platform; making LocalizationInfo public --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 10 +++++- .../Model/LocalizationInfo.cs | 31 +++++++++++++++++-- ...ceLibraries.cs => LocalizableLibraries.cs} | 22 +++++++++++-- KGySoft.Drawing.ImagingTools/Res.cs | 10 +++++- .../View/Forms/EditResourcesForm.cs | 4 +-- .../ViewModel/DownloadResourcesViewModel.cs | 30 +++++++++--------- .../ViewModel/DownloadableResourceItem.cs | 14 +++++++-- .../DownloadableResourceItemCollection.cs | 5 ++- .../ViewModel/EditResourcesViewModel.cs | 26 ++++++++-------- .../ViewModel/LanguageSettingsViewModel.cs | 7 +++-- .../ViewModel/ViewModelFactory.cs | 2 +- .../_Classes/ResHelper.cs | 19 +++++++----- changelog.txt | 2 ++ 13 files changed, 129 insertions(+), 53 deletions(-) rename KGySoft.Drawing.ImagingTools/Model/_Enums/{ResourceLibraries.cs => LocalizableLibraries.cs} (56%) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 170889e..8b5ff9f 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -198,6 +198,9 @@ B: {0} + + Unsupported Language ({0}) + Color Count: {0:N0} @@ -422,6 +425,11 @@ then colors will be quantized to the 32 bit ARGB color space during the conversi The selected quantizer uses more colors than the selected pixel format '{0}' supports. Either select at least {1} pixel format or use another quantizer that uses no more colors than '{0}' can represent; otherwise, the result might not be optimal even with dithering. + + + {0} file(s) have been downloaded. + +The culture of one or more downloaded localizations is not supported on this platform. Those languages will not appear in the list. The selected quantizer supports partial transparency, which is not supported by ditherers, @@ -498,7 +506,7 @@ Dithering may help to preserve more details. The selected pixel format represents a narrower set of colors than the original '{0}'. Dithering may help to preserve more details. - + {0} file(s) have been downloaded. diff --git a/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs b/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs index cbe1ac9..26a5409 100644 --- a/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs +++ b/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs @@ -17,22 +17,47 @@ #region Usings using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; #endregion namespace KGySoft.Drawing.ImagingTools.Model { - internal class LocalizationInfo + /// + /// Represents the metadata of localized resources. + /// + [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global", Justification = "Setter accessors are needed for the deserializer")] + [SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "ReSharper issue")] + public class LocalizationInfo { #region Properties - public CultureInfo Language { get; set; } = default!; + /// + /// Gets or sets the culture name of the localization that matches the property of the represented culture. + /// + public string CultureName { get; set; } = default!; + + /// + /// Gets or sets the description of the localization. + /// public string? Description { get; set; } - public ResourceLibraries ResourceSets { get; set; } = default!; + + /// + /// Gets or sets the version number of the KGySoft.Drawing.ImagingTools.exe assembly this localization belongs to. + /// public Version ImagingToolsVersion { get; set; } = default!; + + /// + /// Gets or sets the author of the localization. + /// public string? Author { get; set; } + /// + /// Gets or sets the class libraries whose resources are covered by this localization. + /// + public LocalizableLibraries ResourceSets { get; set; } = default!; + #endregion } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/Model/_Enums/ResourceLibraries.cs b/KGySoft.Drawing.ImagingTools/Model/_Enums/LocalizableLibraries.cs similarity index 56% rename from KGySoft.Drawing.ImagingTools/Model/_Enums/ResourceLibraries.cs rename to KGySoft.Drawing.ImagingTools/Model/_Enums/LocalizableLibraries.cs index 06257f3..fc7d7b4 100644 --- a/KGySoft.Drawing.ImagingTools/Model/_Enums/ResourceLibraries.cs +++ b/KGySoft.Drawing.ImagingTools/Model/_Enums/LocalizableLibraries.cs @@ -1,7 +1,7 @@ #region Copyright /////////////////////////////////////////////////////////////////////////////// -// File: ResourceLibraries.cs +// File: LocalizableLibraries.cs /////////////////////////////////////////////////////////////////////////////// // Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved // @@ -22,12 +22,30 @@ namespace KGySoft.Drawing.ImagingTools.Model { + /// + /// Represents the known class libraries with localizable resources. + /// [Flags] - internal enum ResourceLibraries + public enum LocalizableLibraries { + /// + /// Represents none of the localizable libraries. + /// None, + + /// + /// Represents the KGySoft.CoreLibraries.dll assembly. + /// CoreLibraries = 1, + + /// + /// Represents the KGySoft.Drawing.dll assembly. + /// DrawingLibraries = 1 << 1, + + /// + /// Represents the KGySoft.Drawing.ImagingTools.exe assembly. + /// ImagingTools = 1 << 2 } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index e8a6861..172b78a 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -356,6 +356,9 @@ internal static void ApplyStringResources(object target, string name) /// B: {0} internal static string TextBlueValue(byte a) => Get("Text_BlueValueFormat", a); + /// Unsupported Language ({0}) + internal static string TextUnsupportedCulture(string cultureName) => Get("Text_UnsupportedCultureFormat", cultureName); + #endregion #region Info Texts @@ -531,6 +534,11 @@ internal static string InfoColor(int argb, string knownColors, string systemColo /// otherwise, the result might not be optimal even with dithering. internal static string WarningMessageQuantizerTooWide(PixelFormat selectedPixelFormat, PixelFormat pixelFormatHint) => Get("WarningMessage_QuantizerTooWideFormat", selectedPixelFormat, pixelFormatHint); + /// {0} file(s) have been downloaded. + /// + /// The culture of one or more downloaded localizations is not supported on this platform. Those languages will not appear in the list. + internal static string WarningMessageDownloadCompletedWithUnsupportedCultures(int count) => Get("WarningMessage_DownloadCompletedWithUnsupportedCulturesFormat", count); + /// The extension of the provided filename '{0}' does not match to the selected format ({1}). /// /// Are you sure you want to save the file with the provided extension? @@ -570,7 +578,7 @@ internal static string InfoColor(int argb, string knownColors, string systemColo internal static string InfoMessageDithererIgnored(PixelFormat pixelFormat) => Get("InfoMessage_DithererIgnoredFormat", pixelFormat); /// {0} file(s) have been downloaded. - internal static string InfoMessageDownloadComplete(int count) => Get("InfoMessage_DownloadCompleteFormat", count); + internal static string InfoMessageDownloadCompleted(int count) => Get("InfoMessage_DownloadCompletedFormat", count); /// About KGy SOFT Imaging Tools /// diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs index 5852481..c616626 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs @@ -36,8 +36,8 @@ internal EditResourcesForm(EditResourcesViewModel viewModel) : base(viewModel) { // Note: Not setting Accept/CancelButton because they would be very annoying during the editing InitializeComponent(); - cmbResourceFiles.ValueMember = nameof(KeyValuePair.Key); - cmbResourceFiles.DisplayMember = nameof(KeyValuePair.Value); + cmbResourceFiles.ValueMember = nameof(KeyValuePair.Key); + cmbResourceFiles.DisplayMember = nameof(KeyValuePair.Value); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index 7453121..011f155 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -19,7 +19,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -36,7 +35,7 @@ namespace KGySoft.Drawing.ImagingTools.ViewModel { - internal class DownloadResourcesViewModel : ViewModelBase, IViewModel> + internal class DownloadResourcesViewModel : ViewModelBase, IViewModel> { #region Nested classes @@ -79,7 +78,7 @@ private sealed class DownloadInfo #region Properties - internal CultureInfo Culture { get; } + internal LocalizationInfo Info { get; } internal string FileName { get; } internal string RemoteUri => $"{remotePath}/{FileName}"; internal string LocalPath => Path.Combine(Res.ResourcesDir, FileName); @@ -88,13 +87,13 @@ private sealed class DownloadInfo #region Constructors - public DownloadInfo(LocalizationInfo info, ResourceLibraries library) + public DownloadInfo(LocalizationInfo info, LocalizableLibraries library) { - Culture = info.Language; - FileName = Equals(info.Language, Res.DefaultLanguage) + Info = info; + FileName = info.CultureName == Res.DefaultLanguage.Name ? ResHelper.GetBaseName(library) + ".resx" - : $"{ResHelper.GetBaseName(library)}.{info.Language.Name}.resx"; - remotePath = $"{info.Language.Name}_{info.Author}_{info.ImagingToolsVersion}"; + : $"{ResHelper.GetBaseName(library)}.{info.CultureName}.resx"; + remotePath = $"{info.CultureName}_{info.Author}_{info.ImagingToolsVersion}"; } #endregion @@ -107,7 +106,7 @@ public DownloadInfo(LocalizationInfo info, ResourceLibraries library) #region Fields private volatile AsyncTaskBase? activeTask; - private volatile HashSet downloadedCultures = new HashSet(); + private volatile HashSet downloadedCultures = new HashSet(); #endregion @@ -262,7 +261,7 @@ private void DoDownloadResources(object state) // if there was no issue with downloading, then saving the file File.WriteAllBytes(downloadInfo.LocalPath, data); - downloadedCultures.Add(downloadInfo.Culture); + downloadedCultures.Add(downloadInfo.Info); IncrementProgress(); downloaded += 1; SetModified(true); @@ -271,7 +270,10 @@ private void DoDownloadResources(object state) TryInvokeSync(() => { IsProcessing = false; - ShowInfo(Res.InfoMessageDownloadComplete(downloaded)); + if (downloadedCultures.All(i => ResHelper.TryGetCulture(i.CultureName, out var _))) + ShowInfo(Res.InfoMessageDownloadCompleted(downloaded)); + else + ShowWarning(Res.WarningMessageDownloadCompletedWithUnsupportedCultures(downloaded)); CloseViewCallback?.Invoke(); }); } @@ -336,9 +338,9 @@ private void IncrementProgress(int value = 1) #endregion - #region Explicitly Implemented Inteface Methods + #region Explicitly Implemented Interface Methods - ICollection IViewModel>.GetEditedModel() => downloadedCultures; + ICollection IViewModel>.GetEditedModel() => downloadedCultures; #endregion @@ -384,7 +386,7 @@ private void OnDownloadCommand() } LocalizationInfo info = item.Info; - foreach (ResourceLibraries lib in info.ResourceSets.GetFlags(false)) + foreach (LocalizableLibraries lib in info.ResourceSets.GetFlags(false)) { var file = new DownloadInfo(info, lib); toDownload.Add(file); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs index 2422d7c..de72515 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs @@ -16,6 +16,7 @@ #region Usings +using System.Globalization; using KGySoft.ComponentModel; using KGySoft.Drawing.ImagingTools.Model; @@ -25,12 +26,21 @@ namespace KGySoft.Drawing.ImagingTools.ViewModel { internal class DownloadableResourceItem : ObservableObjectBase { + #region Fields + + private string? language; + + #endregion + #region Properties #region Public Properties public bool Selected { get => Get(); set => Set(value); } - public string Language => $"{Info.Language.EnglishName} ({Info.Language.NativeName})"; + public string Language => language ??= ResHelper.TryGetCulture(CultureName, out CultureInfo? culture) + ? $"{culture.EnglishName} ({culture.NativeName})" + : Res.TextUnsupportedCulture(CultureName); + public string? Author => Info.Author; public string ImagingToolsVersion => Info.ImagingToolsVersion.ToString(); public string? Description => Info.Description; @@ -41,7 +51,7 @@ internal class DownloadableResourceItem : ObservableObjectBase internal LocalizationInfo Info { get; } - internal string CultureName => Info.Language.Name; + internal string CultureName => Info.CultureName; #endregion diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItemCollection.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItemCollection.cs index a1de97c..718777f 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItemCollection.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItemCollection.cs @@ -42,14 +42,13 @@ internal DownloadableResourceItemCollection(ICollection collec langGroups = new StringKeyedDictionary>(); foreach (LocalizationInfo info in collection) { - string langKey = info.Language.Name; var item = new DownloadableResourceItem(info); item.PropertyChanged += Item_PropertyChanged; - if (langGroups.TryGetValue(langKey, out List? group)) + if (langGroups.TryGetValue(info.CultureName, out List? group)) group.Add(item); else - langGroups[langKey] = new List(1) { item }; + langGroups[info.CultureName] = new List(1) { item }; Add(item); } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index 0181333..bc9f7f7 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -60,15 +60,15 @@ internal class EditResourcesViewModel : ViewModelBase private readonly CultureInfo culture; private readonly bool useInvariant; - private readonly Dictionary ResourceSet, bool IsModified)> resources; + private readonly Dictionary ResourceSet, bool IsModified)> resources; #endregion #region Properties - internal KeyValuePair[] ResourceFiles { get; } // get only because never changes + internal KeyValuePair[] ResourceFiles { get; } // get only because never changes internal string TitleCaption { get => Get(); set => Set(value); } - internal ResourceLibraries SelectedLibrary { get => Get(); set => Set(value); } + internal LocalizableLibraries SelectedLibrary { get => Get(); set => Set(value); } internal IList SelectedSet { get => Get>(); set => Set(value); } internal ICommand ApplyResourcesCommand => Get(() => new SimpleCommand(OnApplyResourcesCommand)); @@ -87,12 +87,12 @@ internal EditResourcesViewModel(CultureInfo culture) // The default language is used as the invariant resource set. // The invariant file name is preferred, unless only the language-specific file exists. - useInvariant = Equals(culture, Res.DefaultLanguage) && !File.Exists(ToFileNameWithPath(ResourceLibraries.ImagingTools)); - resources = new Dictionary, bool)>(3, EnumComparer.Comparer); - ResourceFiles = Enum.GetFlags().Select(lib => new KeyValuePair(lib, ToFileName(lib))).ToArray(); + useInvariant = Equals(culture, Res.DefaultLanguage) && !File.Exists(ToFileNameWithPath(LocalizableLibraries.ImagingTools)); + resources = new Dictionary, bool)>(3, EnumComparer.Comparer); + ResourceFiles = Enum.GetFlags().Select(lib => new KeyValuePair(lib, ToFileName(lib))).ToArray(); ApplyResourcesCommandState.Enabled = !Equals(LanguageSettings.DisplayLanguage, culture); UpdateTitle(); - SelectedLibrary = ResourceLibraries.ImagingTools; + SelectedLibrary = LocalizableLibraries.ImagingTools; } #endregion @@ -109,7 +109,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) switch (e.PropertyName) { case nameof(SelectedLibrary): - UpdateSelectedResources((ResourceLibraries)e.NewValue!); + UpdateSelectedResources((LocalizableLibraries)e.NewValue!); break; } } @@ -134,13 +134,13 @@ protected override void Dispose(bool disposing) private void UpdateTitle() => TitleCaption = Res.TitleEditResources($"{culture.EnglishName} ({culture.NativeName})"); - private string ToFileName(ResourceLibraries library) => useInvariant + private string ToFileName(LocalizableLibraries library) => useInvariant ? ResHelper.GetBaseName(library) + ".resx" : $"{ResHelper.GetBaseName(library)}.{culture.Name}.resx"; - private string ToFileNameWithPath(ResourceLibraries library) => Path.Combine(Res.ResourcesDir, ToFileName(library)); + private string ToFileNameWithPath(LocalizableLibraries library) => Path.Combine(Res.ResourcesDir, ToFileName(library)); - private void UpdateSelectedResources(ResourceLibraries library) + private void UpdateSelectedResources(LocalizableLibraries library) { if (resources.TryGetValue(library, out var value)) { @@ -179,7 +179,7 @@ private void UpdateSelectedResources(ResourceLibraries library) SelectedSet = set; } - private bool TryReadResources(ResourceLibraries library, [MaybeNullWhen(false)]out IList set, [MaybeNullWhen(true)]out Exception error) + private bool TryReadResources(LocalizableLibraries library, [MaybeNullWhen(false)]out IList set, [MaybeNullWhen(true)]out Exception error) { try { @@ -228,7 +228,7 @@ private bool TryReadResources(ResourceLibraries library, [MaybeNullWhen(false)]o } } - private bool TrySaveResources(ResourceLibraries library, IList set, [MaybeNullWhen(true)]out Exception error) + private bool TrySaveResources(LocalizableLibraries library, IList set, [MaybeNullWhen(true)]out Exception error) { // Note: We do not use a DynamicResourceManager for saving. This works because we let the actual DRMs drop their content after saving. try diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index 9face79..adde811 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -21,8 +21,9 @@ using System.ComponentModel; using System.Diagnostics; using System.Globalization; - +using System.Linq; using KGySoft.ComponentModel; +using KGySoft.Drawing.ImagingTools.Model; using KGySoft.Resources; #endregion @@ -264,11 +265,11 @@ private void OnEditResourcesCommand() private void OnDownloadResourcesCommand() { - using IViewModel> viewModel = ViewModelFactory.CreateDownloadResources(); + using IViewModel> viewModel = ViewModelFactory.CreateDownloadResources(); ShowChildViewCallback?.Invoke(viewModel); // If the language was overwritten, then enabling apply even if it was disabled - dirtyCulture = viewModel.IsModified && viewModel.GetEditedModel().Contains(CurrentLanguage) ? CurrentLanguage : null; + dirtyCulture = viewModel.IsModified && viewModel.GetEditedModel().Any(i => i.CultureName == CurrentLanguage.Name) ? CurrentLanguage : null; availableResXLanguages = null; selectableLanguages = null; ResetLanguages(); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs index 8f252a7..b9b004b 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelFactory.cs @@ -251,7 +251,7 @@ public static IViewModel FromGraphics(Graphics? graphics) /// Creates a view model for downloading resources. /// /// An instance that represents a view model for managing language settings. - public static IViewModel> CreateDownloadResources() => new DownloadResourcesViewModel(); + public static IViewModel> CreateDownloadResources() => new DownloadResourcesViewModel(); #endregion } diff --git a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs index 4e26467..32c2a79 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Reflection; @@ -96,19 +97,21 @@ internal static HashSet GetAvailableLanguages() } } - internal static string GetBaseName(ResourceLibraries library) => library switch + internal static bool TryGetCulture(string name, [MaybeNullWhen(false)] out CultureInfo culture) => CulturesCache.TryGetValue(name, out culture); + + internal static string GetBaseName(LocalizableLibraries library) => library switch { - ResourceLibraries.CoreLibraries => coreLibrariesBaseName, - ResourceLibraries.DrawingLibraries => drawingLibrariesBaseName, - ResourceLibraries.ImagingTools => imagingToolsBaseName, + LocalizableLibraries.CoreLibraries => coreLibrariesBaseName, + LocalizableLibraries.DrawingLibraries => drawingLibrariesBaseName, + LocalizableLibraries.ImagingTools => imagingToolsBaseName, _ => throw new ArgumentOutOfRangeException(nameof(library), PublicResources.EnumOutOfRange(library)) }; - internal static Assembly GetAssembly(ResourceLibraries library) => library switch + internal static Assembly GetAssembly(LocalizableLibraries library) => library switch { - ResourceLibraries.CoreLibraries => typeof(LanguageSettings).Assembly, - ResourceLibraries.DrawingLibraries => typeof(DrawingModule).Assembly, - ResourceLibraries.ImagingTools => typeof(Res).Assembly, + LocalizableLibraries.CoreLibraries => typeof(LanguageSettings).Assembly, + LocalizableLibraries.DrawingLibraries => typeof(DrawingModule).Assembly, + LocalizableLibraries.ImagingTools => typeof(Res).Assembly, _ => throw new ArgumentOutOfRangeException(nameof(library), PublicResources.EnumOutOfRange(library)) }; diff --git a/changelog.txt b/changelog.txt index 9ccc35c..9c470f1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -45,6 +45,8 @@ + New ShowDialog(IView) overload + ViewFactory class: + New ShowDialog(IViewModel, IView) overload + + New LocalizableLibraries enum + + New LocalizationInfo class - KGySoft.Drawing.DebuggerVisualizers.dll ========================================= From 03fb7691f2b8927736362efd72d48c86dabf11da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 6 Jun 2021 14:39:58 +0200 Subject: [PATCH 091/211] Language settings: Apply also saves the configuration to prevent inconsistencies when canceling the view --- .../ViewModel/LanguageSettingsViewModel.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index adde811..5ddd66a 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -206,12 +206,12 @@ private void UpdateApplyCommandState() && AvailableLanguages.Contains(Res.DefaultLanguage)); } - #endregion - - #region Command Handlers - - private void OnApplyCommand() + private void ApplyAndSave() { + if (!IsModified) + return; + + // 1. ) Applying the current language dirtyCulture = null; CultureInfo currentLanguage = CurrentLanguage; LanguageSettings.DynamicResourceManagersSource = AllowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; @@ -222,22 +222,12 @@ private void OnApplyCommand() LanguageSettings.DisplayLanguage = currentLanguage; // Note: Ensure is not really needed because main .resx is generated, while others are saved on demand in the editor, too - // TODO If used, then add to EditResourcesVM.Save, too, to be consistent () - //ResHelper.EnsureResourcesGenerated(); + //ResHelper.EnsureResourcesGenerated(); // TODO If used, then add to EditResourcesVM.Save, too, to be consistent ResHelper.SavePendingResources(); availableResXLanguages = null; selectableLanguages = null; - } - private void OnSaveConfigCommand() - { - if (!IsModified) - return; - - if (ApplyCommandState.Enabled) - OnApplyCommand(); - - // saving the configuration + // 2.) Saving the configuration Configuration.AllowResXResources = AllowResXResources; Configuration.UseOSLanguage = UseOSLanguage; Configuration.DisplayLanguage = CurrentLanguage; @@ -249,8 +239,18 @@ private void OnSaveConfigCommand() { ShowError(Res.ErrorMessageFailedToSaveSettings(e.Message)); } + } + #endregion + + #region Command Handlers + + // Both Save and Apply do the same thing. + // The only difference is that Apply has an Enabled state and the View may bind Save to a button that closes the view. + private void OnApplyCommand() => ApplyAndSave(); + private void OnSaveConfigCommand() => ApplyAndSave(); + private void OnEditResourcesCommand() { using IViewModel viewModel = ViewModelFactory.CreateEditResources(CurrentLanguage); From 9cfeca5e301136051ff777b410d7fb881a4b3acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 6 Jun 2021 14:59:39 +0200 Subject: [PATCH 092/211] MainForm: Updating title on language change --- .../View/Forms/AppMainForm.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs index c8ad6d2..c87fb49 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs @@ -31,7 +31,7 @@ internal partial class AppMainForm : ImageVisualizerForm { #region Fields - private static readonly string title = Res.TitleAppNameAndVersion(typeof(Res).Assembly.GetName().Version!); + private string? title; #endregion @@ -85,6 +85,14 @@ private AppMainForm() : this(null!) #region Protected Methods + protected override void ApplyStringResources() + { + base.ApplyStringResources(); + title = Res.TitleAppNameAndVersion(typeof(Res).Assembly.GetName().Version!); + if (CommandBindings.Count > 0) + Text = ViewModel.TitleCaption; + } + protected override void ApplyViewModel() { InitPropertyBindings(); @@ -126,7 +134,7 @@ private string FormatText(string? value) { string? fileName = ViewModel.FileName; string name = fileName == null ? Res.TextUnnamed : Path.GetFileName(fileName); - return String.IsNullOrEmpty(value) ? title : $"{title} [{name}{(ViewModel.IsModified ? "*" : String.Empty)}] - {value}"; + return String.IsNullOrEmpty(value) ? title! : $"{title} [{name}{(ViewModel.IsModified ? "*" : String.Empty)}] - {value}"; } #endregion From f5a6c591871b2ab2af8e73474cab5fb5c9672591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 6 Jun 2021 21:26:59 +0200 Subject: [PATCH 093/211] Editing Resources: The entries in the grid can be filtered --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 9 +- .../View/Forms/EditResourcesForm.Designer.cs | 44 ++++++- .../View/Forms/EditResourcesForm.cs | 7 +- .../ViewModel/EditResourcesViewModel.cs | 114 ++++++++++++++---- 4 files changed, 145 insertions(+), 29 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 8b5ff9f..133560d 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -832,6 +832,13 @@ Newly generated resources will contain English texts prefixed with "[T]". You ca Resource Entries + + Filter: + + + Filtering ignores case, accents and character width. +Tip: use '[T]' to filter untranslated texts. + Resource Key @@ -847,7 +854,7 @@ Newly generated resources will contain English texts prefixed with "[T]". You ca Translated Text - + Downloading Resources diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index a063ba6..a04f0ba 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -37,6 +37,9 @@ private void InitializeComponent() this.colOriginalText = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colTranslatedText = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.bindingSource = new System.Windows.Forms.BindingSource(this.components); + this.pnlFilter = new System.Windows.Forms.Panel(); + this.txtFilter = new System.Windows.Forms.TextBox(); + this.lblFilter = new System.Windows.Forms.Label(); this.gbResourceFile = new System.Windows.Forms.GroupBox(); this.cmbResourceFiles = new System.Windows.Forms.ComboBox(); this.splitterEditResources = new System.Windows.Forms.Splitter(); @@ -49,6 +52,7 @@ private void InitializeComponent() this.gbResourceEntries.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.gridResources)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.bindingSource)).BeginInit(); + this.pnlFilter.SuspendLayout(); this.gbResourceFile.SuspendLayout(); this.pnlEditResourceEntry.SuspendLayout(); this.gbOriginalText.SuspendLayout(); @@ -58,6 +62,7 @@ private void InitializeComponent() // gbResourceEntries // this.gbResourceEntries.Controls.Add(this.gridResources); + this.gbResourceEntries.Controls.Add(this.pnlFilter); this.gbResourceEntries.Dock = System.Windows.Forms.DockStyle.Fill; this.gbResourceEntries.Location = new System.Drawing.Point(3, 49); this.gbResourceEntries.Name = "gbResourceEntries"; @@ -86,9 +91,9 @@ private void InitializeComponent() dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True; this.gridResources.DefaultCellStyle = dataGridViewCellStyle1; this.gridResources.Dock = System.Windows.Forms.DockStyle.Fill; - this.gridResources.Location = new System.Drawing.Point(3, 16); + this.gridResources.Location = new System.Drawing.Point(3, 40); this.gridResources.Name = "gridResources"; - this.gridResources.Size = new System.Drawing.Size(572, 98); + this.gridResources.Size = new System.Drawing.Size(572, 74); this.gridResources.TabIndex = 3; // // colResourceKey @@ -117,6 +122,36 @@ private void InitializeComponent() // this.bindingSource.DataSource = typeof(KGySoft.Drawing.ImagingTools.Model.ResourceEntry); // + // pnlFilter + // + this.pnlFilter.Controls.Add(this.txtFilter); + this.pnlFilter.Controls.Add(this.lblFilter); + this.pnlFilter.Dock = System.Windows.Forms.DockStyle.Top; + this.pnlFilter.Location = new System.Drawing.Point(3, 16); + this.pnlFilter.Name = "pnlFilter"; + this.pnlFilter.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2); + this.pnlFilter.Size = new System.Drawing.Size(572, 24); + this.pnlFilter.TabIndex = 4; + // + // txtFilter + // + this.txtFilter.Dock = System.Windows.Forms.DockStyle.Fill; + this.txtFilter.Location = new System.Drawing.Point(45, 2); + this.txtFilter.Name = "txtFilter"; + this.txtFilter.Size = new System.Drawing.Size(527, 20); + this.txtFilter.TabIndex = 1; + // + // lblFilter + // + this.lblFilter.AutoSize = true; + this.lblFilter.Dock = System.Windows.Forms.DockStyle.Left; + this.lblFilter.Location = new System.Drawing.Point(0, 2); + this.lblFilter.Name = "lblFilter"; + this.lblFilter.Padding = new System.Windows.Forms.Padding(3); + this.lblFilter.Size = new System.Drawing.Size(45, 19); + this.lblFilter.TabIndex = 0; + this.lblFilter.Text = "lblFilter"; + // // gbResourceFile // this.gbResourceFile.Controls.Add(this.cmbResourceFiles); @@ -237,6 +272,8 @@ private void InitializeComponent() this.gbResourceEntries.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.gridResources)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.bindingSource)).EndInit(); + this.pnlFilter.ResumeLayout(false); + this.pnlFilter.PerformLayout(); this.gbResourceFile.ResumeLayout(false); this.pnlEditResourceEntry.ResumeLayout(false); this.gbOriginalText.ResumeLayout(false); @@ -264,5 +301,8 @@ private void InitializeComponent() private System.Windows.Forms.DataGridViewTextBoxColumn colResourceKey; private System.Windows.Forms.DataGridViewTextBoxColumn colOriginalText; private System.Windows.Forms.DataGridViewTextBoxColumn colTranslatedText; + private System.Windows.Forms.Panel pnlFilter; + private System.Windows.Forms.TextBox txtFilter; + private System.Windows.Forms.Label lblFilter; } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs index c616626..96000b9 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs @@ -85,8 +85,11 @@ private void InitPropertyBindings() // VM.SelectedLibrary <-> cmbResourceFiles.SelectedValue CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.SelectedLibrary), cmbResourceFiles, nameof(cmbResourceFiles.SelectedValue)); - // VM.SelectedSet -> bindingSource.DataSource - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.SelectedSet), nameof(bindingSource.DataSource), bindingSource); + // txtFilter.Text -> VM.Filter + CommandBindings.AddPropertyBinding(txtFilter, nameof(txtFilter.Text), nameof(ViewModel.Filter), ViewModel); + + // VM.FilteredSet -> bindingSource.DataSource + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.FilteredSet), nameof(bindingSource.DataSource), bindingSource); // bindingSource.OriginalText -> txtOriginalText.Text txtOriginalText.DataBindings.Add(nameof(txtOriginalText.Text), bindingSource, nameof(ResourceEntry.OriginalText), false, DataSourceUpdateMode.Never); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index bc9f7f7..8ceb841 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -69,7 +69,8 @@ internal class EditResourcesViewModel : ViewModelBase internal KeyValuePair[] ResourceFiles { get; } // get only because never changes internal string TitleCaption { get => Get(); set => Set(value); } internal LocalizableLibraries SelectedLibrary { get => Get(); set => Set(value); } - internal IList SelectedSet { get => Get>(); set => Set(value); } + internal string Filter { get => Get(""); set => Set(value); } + internal IList? FilteredSet { get => Get?>(); set => Set(value); } internal ICommand ApplyResourcesCommand => Get(() => new SimpleCommand(OnApplyResourcesCommand)); internal ICommand SaveResourcesCommand => Get(() => new SimpleCommand(OnSaveResourcesCommand)); @@ -89,6 +90,7 @@ internal EditResourcesViewModel(CultureInfo culture) // The invariant file name is preferred, unless only the language-specific file exists. useInvariant = Equals(culture, Res.DefaultLanguage) && !File.Exists(ToFileNameWithPath(LocalizableLibraries.ImagingTools)); resources = new Dictionary, bool)>(3, EnumComparer.Comparer); + Set(String.Empty, false, nameof(Filter)); ResourceFiles = Enum.GetFlags().Select(lib => new KeyValuePair(lib, ToFileName(lib))).ToArray(); ApplyResourcesCommandState.Enabled = !Equals(LanguageSettings.DisplayLanguage, culture); UpdateTitle(); @@ -109,7 +111,8 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) switch (e.PropertyName) { case nameof(SelectedLibrary): - UpdateSelectedResources((LocalizableLibraries)e.NewValue!); + case nameof(Filter): + ApplySelection(); break; } } @@ -124,7 +127,13 @@ protected override void Dispose(bool disposing) { if (IsDisposed) return; - resources.Values.ForEach(v => (v.ResourceSet as IDisposable)?.Dispose()); + + if (disposing) + { + resources.Values.ForEach(v => (v.ResourceSet as IDisposable)?.Dispose()); + (FilteredSet as IDisposable)?.Dispose(); + } + base.Dispose(disposing); } @@ -140,11 +149,13 @@ private string ToFileName(LocalizableLibraries library) => useInvariant private string ToFileNameWithPath(LocalizableLibraries library) => Path.Combine(Res.ResourcesDir, ToFileName(library)); - private void UpdateSelectedResources(LocalizableLibraries library) + private void ApplySelection() { + LocalizableLibraries library = SelectedLibrary; + if (resources.TryGetValue(library, out var value)) { - SelectedSet = value.ResourceSet; + ApplyFilter(value.ResourceSet); return; } @@ -152,7 +163,7 @@ private void UpdateSelectedResources(LocalizableLibraries library) { if (!Confirm(Res.ConfirmMessageTryRegenerateResource(ToFileName(library), error.Message))) { - SelectedSet = Reflector.EmptyArray(); + ApplyFilter(Reflector.EmptyArray()); return; } @@ -163,20 +174,58 @@ private void UpdateSelectedResources(LocalizableLibraries library) catch (Exception e) when (!e.IsCritical()) { ShowError(Res.ErrorMessageFailedToRegenerateResource(ToFileName(library), error.Message)); - SelectedSet = Reflector.EmptyArray(); + ApplyFilter(Reflector.EmptyArray()); return; } if (!TryReadResources(library, out set, out error)) { ShowError(Res.ErrorMessageFailedToRegenerateResource(ToFileName(library), error.Message)); - SelectedSet = Reflector.EmptyArray(); + ApplyFilter(Reflector.EmptyArray()); return; } } resources[library] = (set, false); - SelectedSet = set; + ApplyFilter(set); + } + + private void ApplyFilter(IList set) + { + var oldSet = FilteredSet as SortableBindingList; + if (oldSet != null) + oldSet.ListChanged -= FilteredSet_ListChanged; + + if (set.Count == 0) + { + FilteredSet = set; + return; + } + + Debug.Assert(set is SortableBindingList, "Non-empty set is expected to be a SortableBindingList"); + string filter = Filter; + SortableBindingList newSet; + if (filter.Length == 0) + newSet = (SortableBindingList)set; + else + { + newSet = new SortableBindingList(new List()); + CompareInfo compareInfo = culture.CompareInfo; + CompareOptions options = CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreWidth; + foreach (ResourceEntry entry in set) + { + if (compareInfo.IndexOf(entry.Key, filter, options) >= 0 + || compareInfo.IndexOf(entry.OriginalText, filter, options) >= 0 + || compareInfo.IndexOf(entry.TranslatedText, filter, options) >= 0) + { + newSet.Add(entry); + } + } + } + + newSet.ListChanged += FilteredSet_ListChanged; + ApplySort(newSet); + FilteredSet = newSet; } private bool TryReadResources(LocalizableLibraries library, [MaybeNullWhen(false)]out IList set, [MaybeNullWhen(true)]out Exception error) @@ -199,23 +248,10 @@ private bool TryReadResources(LocalizableLibraries library, [MaybeNullWhen(false // Note: this way we add even possibly missing entries that were added to compiled resources since last creation while removed entries will be skipped var result = new SortableBindingList(); foreach (DictionaryEntry entry in compiled) - result.Add(new ResourceEntry((string)entry.Key, - (string)entry.Value, + result.Add(new ResourceEntry((string)entry.Key, + (string)entry.Value, resourceManger.GetString((string)entry.Key, culture) ?? LanguageSettings.UntranslatedResourcePrefix + (string)entry.Value)); - result.ListChanged += (_, args) => - { - if (args.ListChangedType != ListChangedType.ItemChanged) - return; - - ApplyResourcesCommandState.Enabled = true; - if (!resources.TryGetValue(library, out var value) || value.IsModified) - return; - - resources[library] = (value.ResourceSet, true); - SetModified(true); - }; - result.ApplySort(nameof(ResourceEntry.Key), ListSortDirection.Ascending); error = null; set = result; return true; @@ -228,6 +264,18 @@ private bool TryReadResources(LocalizableLibraries library, [MaybeNullWhen(false } } + private void ApplySort(SortableBindingList set) + { + var hint = FilteredSet as IBindingList; + ListSortDirection direction = hint?.IsSorted == true ? hint.SortDirection : ListSortDirection.Ascending; + PropertyDescriptor? sortProperty = hint?.SortProperty; + + if (sortProperty != null) + set.ApplySort(sortProperty, direction); + else + set.ApplySort(nameof(ResourceEntry.Key), direction); + } + private bool TrySaveResources(LocalizableLibraries library, IList set, [MaybeNullWhen(true)]out Exception error) { // Note: We do not use a DynamicResourceManager for saving. This works because we let the actual DRMs drop their content after saving. @@ -278,6 +326,24 @@ private void ApplyResources() SetModified(false); } + #endregion + + #region Event Handlers + + private void FilteredSet_ListChanged(object sender, ListChangedEventArgs e) + { + if (e.ListChangedType != ListChangedType.ItemChanged) + return; + + ApplyResourcesCommandState.Enabled = true; + LocalizableLibraries library = SelectedLibrary; + if (resources.TryGetValue(library, out var value) && !value.IsModified) + resources[library] = (value.ResourceSet, true); + + SetModified(true); + } + + #endregion #region Command Handlers From 23488d2aa4cfbb5890ae303ff790dcd8bbafaed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 7 Jun 2021 18:39:38 +0200 Subject: [PATCH 094/211] Edit resources: accepting TAB --- .../View/Forms/EditResourcesForm.Designer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index a04f0ba..6321009 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -236,6 +236,7 @@ private void InitializeComponent() // // txtTranslatedText // + this.txtTranslatedText.AcceptsTab = true; this.txtTranslatedText.Dock = System.Windows.Forms.DockStyle.Fill; this.txtTranslatedText.Location = new System.Drawing.Point(3, 16); this.txtTranslatedText.Multiline = true; From 2edea1f9c48fcb65c4d7db2c3ba9ec2bffdf181c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 9 Jun 2021 11:11:26 +0200 Subject: [PATCH 095/211] AutoMirrorPanel for supporting RTL layout --- .../View/Controls/AutoMirrorPanel.cs | 72 +++++++++++++++++++ .../View/Forms/EditResourcesForm.Designer.cs | 4 +- .../Forms/ManageInstallationsForm.Designer.cs | 13 ++-- .../Forms/TransformBitmapFormBase.Designer.cs | 4 +- 4 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/View/Controls/AutoMirrorPanel.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AutoMirrorPanel.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AutoMirrorPanel.cs new file mode 100644 index 0000000..71fbae6 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AutoMirrorPanel.cs @@ -0,0 +1,72 @@ +#region Usings + +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View.Controls +{ + /// + /// Just for mirroring content for RTL languages. + /// In this project all relevant controls are docked so handling the Dock property only. + /// + internal class AutoMirrorPanel : Panel + { + #region Fields + + private readonly List toBeAdjusted = new List(); + + #endregion + + #region Methods + + protected override void OnControlAdded(ControlEventArgs e) + { + // there is no public IsLayoutSuspended property but in this project we can assume that controls are either added + // in suspended state or before setting RightToLeft + base.OnControlAdded(e); + toBeAdjusted.Add(e.Control); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + base.OnLayout(levent); + if (toBeAdjusted.Count == 0) + return; + + if (RightToLeft == RightToLeft.Yes) + { + foreach (Control control in toBeAdjusted) + { + // Adjusting docking only + DockStyle dockStyle = control.Dock; + if (dockStyle == DockStyle.Left) + control.Dock = DockStyle.Right; + else if (dockStyle == DockStyle.Right) + control.Dock = DockStyle.Left; + } + } + + toBeAdjusted.Clear(); + } + + protected override void OnRightToLeftChanged(EventArgs e) + { + base.OnRightToLeftChanged(e); + toBeAdjusted.Clear(); + foreach (Control control in Controls) + { + // Adjusting docking only + DockStyle dockStyle = control.Dock; + if (dockStyle == DockStyle.Left) + control.Dock = DockStyle.Right; + else if (dockStyle == DockStyle.Right) + control.Dock = DockStyle.Left; + } + } + + #endregion + } +} diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index 6321009..2baac46 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -37,7 +37,7 @@ private void InitializeComponent() this.colOriginalText = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colTranslatedText = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.bindingSource = new System.Windows.Forms.BindingSource(this.components); - this.pnlFilter = new System.Windows.Forms.Panel(); + this.pnlFilter = new Controls.AutoMirrorPanel(); this.txtFilter = new System.Windows.Forms.TextBox(); this.lblFilter = new System.Windows.Forms.Label(); this.gbResourceFile = new System.Windows.Forms.GroupBox(); @@ -302,7 +302,7 @@ private void InitializeComponent() private System.Windows.Forms.DataGridViewTextBoxColumn colResourceKey; private System.Windows.Forms.DataGridViewTextBoxColumn colOriginalText; private System.Windows.Forms.DataGridViewTextBoxColumn colTranslatedText; - private System.Windows.Forms.Panel pnlFilter; + private Controls.AutoMirrorPanel pnlFilter; private System.Windows.Forms.TextBox txtFilter; private System.Windows.Forms.Label lblFilter; } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs index cf66e6e..9ee62de 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs @@ -21,7 +21,7 @@ private void InitializeComponent() this.tblButtons = new System.Windows.Forms.TableLayoutPanel(); this.btnRemove = new System.Windows.Forms.Button(); this.btnInstall = new System.Windows.Forms.Button(); - this.pnlStatus = new System.Windows.Forms.Panel(); + this.pnlStatus = new KGySoft.Drawing.ImagingTools.View.Controls.AutoMirrorPanel(); this.lblStatusText = new System.Windows.Forms.Label(); this.lblStatus = new System.Windows.Forms.Label(); this.tbPath = new System.Windows.Forms.TextBox(); @@ -135,12 +135,11 @@ private void InitializeComponent() // // lblPath // - this.lblPath.AutoSize = true; this.lblPath.Dock = System.Windows.Forms.DockStyle.Top; this.lblPath.FlatStyle = System.Windows.Forms.FlatStyle.System; this.lblPath.Location = new System.Drawing.Point(3, 16); this.lblPath.Name = "lblPath"; - this.lblPath.Size = new System.Drawing.Size(39, 13); + this.lblPath.Size = new System.Drawing.Size(372, 13); this.lblPath.TabIndex = 0; this.lblPath.Text = "lblPath"; // @@ -179,12 +178,11 @@ private void InitializeComponent() // // lblAvailableVersion // - this.lblAvailableVersion.AutoSize = true; - this.lblAvailableVersion.Dock = System.Windows.Forms.DockStyle.Left; + this.lblAvailableVersion.Dock = System.Windows.Forms.DockStyle.Fill; this.lblAvailableVersion.FlatStyle = System.Windows.Forms.FlatStyle.System; this.lblAvailableVersion.Location = new System.Drawing.Point(3, 16); this.lblAvailableVersion.Name = "lblAvailableVersion"; - this.lblAvailableVersion.Size = new System.Drawing.Size(95, 13); + this.lblAvailableVersion.Size = new System.Drawing.Size(372, 21); this.lblAvailableVersion.TabIndex = 0; this.lblAvailableVersion.Text = "lblAvailableVersion"; // @@ -209,7 +207,6 @@ private void InitializeComponent() this.pnlStatus.PerformLayout(); this.gbVisualStudioVersions.ResumeLayout(false); this.gbAvailableVersion.ResumeLayout(false); - this.gbAvailableVersion.PerformLayout(); this.ResumeLayout(false); } @@ -219,7 +216,7 @@ private void InitializeComponent() private System.Windows.Forms.TableLayoutPanel tblButtons; private System.Windows.Forms.Button btnRemove; private System.Windows.Forms.Button btnInstall; - private System.Windows.Forms.Panel pnlStatus; + private Controls.AutoMirrorPanel pnlStatus; private System.Windows.Forms.Label lblStatusText; private System.Windows.Forms.Label lblStatus; private System.Windows.Forms.TextBox tbPath; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs index 52bc769..df3d047 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs @@ -19,7 +19,7 @@ private void InitializeComponent() this.progress = new KGySoft.Drawing.ImagingTools.View.Controls.DrawingProgressStatusStrip(); this.okCancelButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); this.previewImage = new KGySoft.Drawing.ImagingTools.View.UserControls.PreviewImageControl(); - this.pnlSettings = new System.Windows.Forms.Panel(); + this.pnlSettings = new Controls.AutoMirrorPanel(); this.errorProvider = new System.Windows.Forms.ErrorProvider(this.components); this.warningProvider = new System.Windows.Forms.ErrorProvider(this.components); this.infoProvider = new System.Windows.Forms.ErrorProvider(this.components); @@ -99,7 +99,7 @@ private void InitializeComponent() private Controls.DrawingProgressStatusStrip progress; private UserControls.OkCancelButtons okCancelButtons; private UserControls.PreviewImageControl previewImage; - protected System.Windows.Forms.Panel pnlSettings; + protected Controls.AutoMirrorPanel pnlSettings; private System.Windows.Forms.ErrorProvider warningProvider; private System.Windows.Forms.ErrorProvider infoProvider; private System.Windows.Forms.ErrorProvider errorProvider; From fabcec479b2150471b0d400ddf03dca3984cf45b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 9 Jun 2021 11:27:38 +0200 Subject: [PATCH 096/211] Fixing next/previous image navigation for right-to-left cultures --- .../View/Forms/ImageVisualizerForm.Designer.cs | 2 ++ .../View/Forms/ImageVisualizerForm.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs index 30beb81..215d2f0 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs @@ -337,6 +337,7 @@ private void InitializeComponent() this.btnPrev.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; this.btnPrev.ImageTransparentColor = System.Drawing.Color.Magenta; this.btnPrev.Name = "btnPrev"; + this.btnPrev.RightToLeftAutoMirrorImage = true; this.btnPrev.Size = new System.Drawing.Size(23, 22); // // btnNext @@ -344,6 +345,7 @@ private void InitializeComponent() this.btnNext.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; this.btnNext.ImageTransparentColor = System.Drawing.Color.Magenta; this.btnNext.Name = "btnNext"; + this.btnNext.RightToLeftAutoMirrorImage = true; this.btnNext.Size = new System.Drawing.Size(23, 22); // // toolStripSeparator4 diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index 84db4ac..1d75d68 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -153,10 +153,10 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData) btnAntiAlias.PerformClick(); return true; case Keys.Shift | Keys.Right: - btnNext.PerformClick(); + (RightToLeft == RightToLeft.Yes ? btnPrev : btnNext).PerformClick(); return true; case Keys.Shift | Keys.Left: - btnPrev.PerformClick(); + (RightToLeft == RightToLeft.Yes ? btnNext : btnPrev).PerformClick(); return true; default: return base.ProcessCmdKey(ref msg, keyData); From c4700976f5d0bd2b716f2e6b290ca4a335acf0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 9 Jun 2021 13:54:33 +0200 Subject: [PATCH 097/211] Comments --- .../ViewModel/DownloadableResourceItem.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs index de72515..b1102ae 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs @@ -17,6 +17,7 @@ #region Usings using System.Globalization; + using KGySoft.ComponentModel; using KGySoft.Drawing.ImagingTools.Model; @@ -37,6 +38,10 @@ internal class DownloadableResourceItem : ObservableObjectBase #region Public Properties public bool Selected { get => Get(); set => Set(value); } + + // Note: this could be also observable and we could subscribe language change just to adjust unsupported culture + // on-the-fly but this will never happen unless using this class from API. Subscribing language change from every item + // just for updating the possible unsupported cultures is not worth it. public string Language => language ??= ResHelper.TryGetCulture(CultureName, out CultureInfo? culture) ? $"{culture.EnglishName} ({culture.NativeName})" : Res.TextUnsupportedCulture(CultureName); @@ -59,7 +64,7 @@ internal class DownloadableResourceItem : ObservableObjectBase #region Constructors - internal DownloadableResourceItem(LocalizationInfo info) => this.Info = info; + internal DownloadableResourceItem(LocalizationInfo info) => Info = info; #endregion } From e7ca1a0015f856170741cbc2ff314f8725dd1ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 10 Jun 2021 12:52:48 +0200 Subject: [PATCH 098/211] Adding validation to PlaceholderEntry --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 9 + .../Model/ResourceEntry.cs | 285 +++++++++++++++++- KGySoft.Drawing.ImagingTools/Res.cs | 9 + 3 files changed, 302 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 133560d..6b624be 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -329,6 +329,12 @@ Brightness: {5:F0}% The current installation is being executed, which cannot be removed + + Resource format string is invalid. + + + One or more placeholder is missing from the translated resource format string. + Could not save image due to an error: {0} @@ -399,6 +405,9 @@ Either select at least '{1}' or reduce the number of colors to {2}. Failed to download resource file {0}: {1} + + Index '{0}' is invalid in the translated resource format string. + Could not create directory {0}: {1} diff --git a/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs b/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs index b8847b6..f325b7a 100644 --- a/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs +++ b/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs @@ -16,14 +16,37 @@ #region Usings +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; + using KGySoft.ComponentModel; #endregion namespace KGySoft.Drawing.ImagingTools.Model { - internal class ResourceEntry : ObservableObjectBase + internal class ResourceEntry : ValidatingObjectBase { + #region Nested Types + + private enum State + { + Text, + Index, + Padding, + Format + } + + #endregion + + #region Fields + + private int? placeholderCount; + + #endregion + #region Properties public string Key { get; } @@ -42,5 +65,265 @@ internal ResourceEntry(string key, string originalText, string translatedText) } #endregion + + #region Methods + + #region Protected Methods + + protected override ValidationResultsCollection DoValidation() + { + #region Local Methods + + static void AddFormatError(ValidationResultsCollection validationResults) => validationResults.AddError(nameof(TranslatedText), Res.ErrorMessageResourceFormatError); + + bool HandlePlaceholder(ValidationResultsCollection validationResults, int index, ref int used) + { + if (index > placeholderCount - 1) + { + validationResults.AddError(nameof(TranslatedText), Res.ErrorMessageResourcePlaceholderIndexInvalid(index)); + return false; + } + + used |= 1 << index; + return true; + } + + #endregion + + var result = new ValidationResultsCollection(); + EnsurePlaceholderCount(); + if (placeholderCount == 0) + return result; + + Debug.Assert(placeholderCount < 32, "No resource is expected to contain more than 32 placeholders in KGy SOFT Libraries"); + int usedPlaceholders = 0; + string value = TranslatedText; + var state = State.Text; + int currentIndex = 0; + using var reader = new StringReader(value); + while (reader.Read() is int c and >= 0) + { + switch (state) + { + // Out of placeholder part + case State.Text: + + switch (c) + { + // { - placeholder candidate: looking ahead one character + case '{': + switch (c = reader.Read()) + { + // {{ - escape + case '{': + continue; + + // 0..9 - an actual placeholder + case >= '0' and <= '9': + currentIndex = c - '0'; + state = State.Index; + continue; + + // other character or end of string (whitespace is not allowed here) + default: + AddFormatError(result); + return result; + } + + // } - only escape is allowed as we are not in placeholder now + case '}': + if (reader.Read() == '}') + continue; + AddFormatError(result); + return result; + + // anything else - staying in text + default: + continue; + } + + // Inside placeholder index + case State.Index: + + // more digits: staying in index + if (c is >= '0' and <= '9') + { + currentIndex *= 10; + currentIndex += c - '0'; + continue; + } + + // consuming possible spaces (no other whitespace is allowed) + while (c == ' ') + c = reader.Read(); + + switch (c) + { + // end of placeholder + case '}': + if (!HandlePlaceholder(result, currentIndex, ref usedPlaceholders)) + return result; + state = State.Text; + continue; + + // padding + case ',': + state = State.Padding; + continue; + + // format specifier + case ':': + state = State.Format; + continue; + + default: + AddFormatError(result); + return result; + } + + // Inside placeholder padding + case State.Padding: + + // consuming possible spaces before padding count + while (c == ' ') + c = reader.Read(); + + // consuming possible negative sign + if (c == '-') + c = reader.Read(); + + bool hasDigit = false; + + // consuming digits: staying in index + while (c is >= '0' and <= '9') + { + c = reader.Read(); + hasDigit = true; + } + + // consuming possible spaces after padding count + while (c == ' ') + c = reader.Read(); + + if (!hasDigit) + { + AddFormatError(result); + return result; + } + + switch (c) + { + // end of placeholder + case '}': + if (!HandlePlaceholder(result, currentIndex, ref usedPlaceholders)) + return result; + state = State.Text; + continue; + + // format specifier + case ':': + state = State.Format; + continue; + + default: + AddFormatError(result); + return result; + } + + // Inside placeholder format specifier + case State.Format: + switch (c) + { + // possible end of placeholder: looking ahead one character + case '}': + if (reader.Peek() == '}') + { + reader.Read(); + continue; + } + + if (!HandlePlaceholder(result, currentIndex, ref usedPlaceholders)) + return result; + state = State.Text; + continue; + + // in format specifier { is allowed only escaped as part of the format + case '{': + if (reader.Peek() == '{') + { + reader.Read(); + continue; + } + + AddFormatError(result); + return result; + + default: + continue; + } + } + } + + if (state != State.Text) + AddFormatError(result); + else if (usedPlaceholders != (1 << placeholderCount) - 1) + result.AddError(nameof(TranslatedText), Res.ErrorMessageResourcePlaceholderUnusedIndices); + return result; + } + + #endregion + + #region Private Methods + + private void EnsurePlaceholderCount() + { + if (placeholderCount.HasValue) + return; + + // Just a shortcut: in all KGy SOFT Libraries format string resources end with 'Format' + // Note: there are some resources that end with 'Format' and they are not format strings (eg. InfoMessage_SamePixelFormat) + // but they contain no '{' so there will be no misinterpretation. + if (!Key.EndsWith("Format", StringComparison.Ordinal)) + { + placeholderCount = 0; + return; + } + + // -2: a placeholder is at least 3 chars long + int len = OriginalText.Length - 2; + int max = -1; + for (int i = 0; i < len; i++) + { + if (OriginalText[i] != '{') + continue; + + if (OriginalText[i + 1] == '{') + { + i += 1; + continue; + } + + int posEnd = OriginalText.IndexOf('}', i + 2); + Debug.Assert(posEnd > 1, "Valid original formats are expected"); + int posPadding = OriginalText.IndexOf(',', i + 2); + if (posPadding < i || posPadding > posEnd) + posPadding = posEnd; + int posFormat = OriginalText.IndexOf(':', i + 2); + if (posFormat < i || posFormat > posEnd) + posFormat = posEnd; + + int indexLen = Math.Min(posEnd, Math.Min(posPadding, posFormat)) - 1 - i; + int index = Int32.Parse(OriginalText.Substring(i + 1, indexLen), NumberStyles.None, CultureInfo.InvariantCulture); + if (max < index) + max = index; + i = posEnd; + } + + placeholderCount = max + 1; + } + + #endregion + + #endregion } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 172b78a..ffb2d9e 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -187,6 +187,12 @@ internal static class Res /// The current installation is being executed, which cannot be removed internal static string ErrorMessageInstallationCannotBeRemoved => Get("ErrorMessage_InstallationCannotBeRemoved"); + /// Resource format string is invalid. + internal static string ErrorMessageResourceFormatError => Get("ErrorMessage_ResourceFormatError"); + + /// One or more placeholder is missing from the translated resource format string. + internal static string ErrorMessageResourcePlaceholderUnusedIndices => Get("ErrorMessage_ResourcePlaceholderUnusedIndices"); + /// The selected quantizer supports partial transparency, which is not supported by ditherers, /// so partial transparent pixels will be blended with back color. internal static string WarningMessageDithererNoAlphaGradient => Get("WarningMessage_DithererNoAlphaGradient"); @@ -505,6 +511,9 @@ internal static string InfoColor(int argb, string knownColors, string systemColo /// Failed to download resource file {0}: {1} internal static string ErrorMessageFailedToDownloadResource(string fileName, string message) => Get("ErrorMessage_FailedToDownloadResourceFormat", fileName, message); + /// Index '{0}' is invalid in the translated resource format string. + internal static string ErrorMessageResourcePlaceholderIndexInvalid(int index) => Get("ErrorMessage_ResourcePlaceholderIndexInvalidFormat", index); + #if NET45 /// Could not create directory {0}: {1} /// From c87f03ac9050e2f961c819794c4418061e0bd149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 10 Jun 2021 15:11:09 +0200 Subject: [PATCH 099/211] Fixing arrange on Linux --- .../View/Forms/LanguageSettingsForm.Designer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs index 04d8b30..7c45cac 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs @@ -86,7 +86,7 @@ private void InitializeComponent() this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); this.tableLayoutPanel1.Controls.Add(this.btnDownloadResources, 0, 1); - this.tableLayoutPanel1.Controls.Add(this.btnEditResources, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.btnEditResources, 1, 0); this.tableLayoutPanel1.Controls.Add(this.cmbLanguages, 0, 0); this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 16); From 2098ff00ef279b6e280de7235110ae0b920f8c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 10 Jun 2021 15:50:29 +0200 Subject: [PATCH 100/211] Edit resources: adding error provider --- .../View/Forms/EditResourcesForm.Designer.cs | 10 ++++++- .../View/Forms/EditResourcesForm.cs | 28 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index 2baac46..769371e 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -37,7 +37,7 @@ private void InitializeComponent() this.colOriginalText = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colTranslatedText = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.bindingSource = new System.Windows.Forms.BindingSource(this.components); - this.pnlFilter = new Controls.AutoMirrorPanel(); + this.pnlFilter = new KGySoft.Drawing.ImagingTools.View.Controls.AutoMirrorPanel(); this.txtFilter = new System.Windows.Forms.TextBox(); this.lblFilter = new System.Windows.Forms.Label(); this.gbResourceFile = new System.Windows.Forms.GroupBox(); @@ -49,6 +49,7 @@ private void InitializeComponent() this.gbTranslatedText = new System.Windows.Forms.GroupBox(); this.txtTranslatedText = new System.Windows.Forms.TextBox(); this.okCancelApplyButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); + this.errorProvider = new System.Windows.Forms.ErrorProvider(this.components); this.gbResourceEntries.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.gridResources)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.bindingSource)).BeginInit(); @@ -57,6 +58,7 @@ private void InitializeComponent() this.pnlEditResourceEntry.SuspendLayout(); this.gbOriginalText.SuspendLayout(); this.gbTranslatedText.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.errorProvider)).BeginInit(); this.SuspendLayout(); // // gbResourceEntries @@ -255,6 +257,10 @@ private void InitializeComponent() this.okCancelApplyButtons.Size = new System.Drawing.Size(578, 35); this.okCancelApplyButtons.TabIndex = 1; // + // errorProvider + // + this.errorProvider.ContainerControl = this; + // // EditResourcesForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -281,6 +287,7 @@ private void InitializeComponent() this.gbOriginalText.PerformLayout(); this.gbTranslatedText.ResumeLayout(false); this.gbTranslatedText.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.errorProvider)).EndInit(); this.ResumeLayout(false); } @@ -305,5 +312,6 @@ private void InitializeComponent() private Controls.AutoMirrorPanel pnlFilter; private System.Windows.Forms.TextBox txtFilter; private System.Windows.Forms.Label lblFilter; + private System.Windows.Forms.ErrorProvider errorProvider; } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs index 96000b9..201f583 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs @@ -17,6 +17,7 @@ #region Usings using System.Collections.Generic; +using System.Linq; using System.Windows.Forms; using KGySoft.Drawing.ImagingTools.Model; @@ -38,6 +39,7 @@ internal EditResourcesForm(EditResourcesViewModel viewModel) : base(viewModel) InitializeComponent(); cmbResourceFiles.ValueMember = nameof(KeyValuePair.Key); cmbResourceFiles.DisplayMember = nameof(KeyValuePair.Value); + errorProvider.SetIconAlignment(gbTranslatedText, ErrorIconAlignment.MiddleLeft); } #endregion @@ -61,6 +63,7 @@ protected override void ApplyResources() { base.ApplyResources(); Icon = Properties.Resources.Language; + errorProvider.Icon = Icons.SystemError.ToScaledIcon(); } protected override void ApplyViewModel() @@ -96,6 +99,10 @@ private void InitPropertyBindings() // bindingSource.TranslatedText <-> txtTranslatedText.Text txtTranslatedText.DataBindings.Add(nameof(txtTranslatedText.Text), bindingSource, nameof(ResourceEntry.TranslatedText), false, DataSourceUpdateMode.OnValidation); + + // this.RightToLeft -> errorProvider.RightToLeft + CommandBindings.AddPropertyBinding(this, nameof(RightToLeft), nameof(ErrorProvider.RightToLeft), + rtl => rtl is RightToLeft.Yes, errorProvider); } private void InitCommandBindings() @@ -112,6 +119,27 @@ private void InitCommandBindings() // CancelButton.Click -> ViewModel.CancelResourcesCommand CommandBindings.Add(ViewModel.CancelEditCommand) .AddSource(okCancelApplyButtons.CancelButton, nameof(okCancelApplyButtons.CancelButton.Click)); + + // View commands + CommandBindings.Add(OnCurrentItemChangedCommand) + .AddSource(bindingSource, nameof(bindingSource.CurrentItemChanged)); + + } + + #endregion + + #region Command Handlers + + private void OnCurrentItemChangedCommand() + { + var current = bindingSource.Current as ResourceEntry; + if (current == null) + { + errorProvider.SetError(gbTranslatedText, null); + return; + } + + errorProvider.SetError(gbTranslatedText, current.ValidationResults.Errors.FirstOrDefault()?.Message); } #endregion From db5d93c99a81c636eb0677d8adb5fcb62eeeb5f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 10 Jun 2021 16:09:46 +0200 Subject: [PATCH 101/211] Fixing right to left appearance and tab order --- .../View/Forms/ResizeBitmapForm.Designer.cs | 82 ++++++++----------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs index 2379d45..7c297bf 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs @@ -18,18 +18,18 @@ private void InitializeComponent() this.tblNewSize = new System.Windows.Forms.TableLayoutPanel(); this.chbMaintainAspectRatio = new System.Windows.Forms.CheckBox(); this.lblScalingMode = new System.Windows.Forms.Label(); - this.pnlHeightPx = new System.Windows.Forms.Panel(); + this.pnlHeightPx = new KGySoft.Drawing.ImagingTools.View.Controls.AutoMirrorPanel(); this.lblHeightPx = new System.Windows.Forms.Label(); this.txtHeightPx = new System.Windows.Forms.TextBox(); - this.pnlHeightPercent = new System.Windows.Forms.Panel(); + this.pnlHeightPercent = new KGySoft.Drawing.ImagingTools.View.Controls.AutoMirrorPanel(); this.lblHeightPercent = new System.Windows.Forms.Label(); this.txtHeightPercent = new System.Windows.Forms.TextBox(); - this.pnlWidthPx = new System.Windows.Forms.Panel(); + this.pnlWidthPx = new KGySoft.Drawing.ImagingTools.View.Controls.AutoMirrorPanel(); this.lblWidthPx = new System.Windows.Forms.Label(); this.txtWidthPx = new System.Windows.Forms.TextBox(); this.rbByPixels = new System.Windows.Forms.RadioButton(); this.rbByPercentage = new System.Windows.Forms.RadioButton(); - this.pnlWidthPercent = new System.Windows.Forms.Panel(); + this.pnlWidthPercent = new KGySoft.Drawing.ImagingTools.View.Controls.AutoMirrorPanel(); this.lblWidthPercent = new System.Windows.Forms.Label(); this.txtWidthPercent = new System.Windows.Forms.TextBox(); this.lblWidth = new System.Windows.Forms.Label(); @@ -47,7 +47,7 @@ private void InitializeComponent() // this.pnlSettings.Controls.Add(this.tblNewSize); this.pnlSettings.Padding = new System.Windows.Forms.Padding(5); - this.pnlSettings.Size = new System.Drawing.Size(314, 143); + this.pnlSettings.Size = new System.Drawing.Size(334, 143); // // tblNewSize // @@ -75,7 +75,7 @@ private void InitializeComponent() this.tblNewSize.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); this.tblNewSize.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); this.tblNewSize.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tblNewSize.Size = new System.Drawing.Size(304, 137); + this.tblNewSize.Size = new System.Drawing.Size(324, 137); this.tblNewSize.TabIndex = 0; // // chbMaintainAspectRatio @@ -86,7 +86,7 @@ private void InitializeComponent() this.chbMaintainAspectRatio.FlatStyle = System.Windows.Forms.FlatStyle.System; this.chbMaintainAspectRatio.Location = new System.Drawing.Point(103, 3); this.chbMaintainAspectRatio.Name = "chbMaintainAspectRatio"; - this.chbMaintainAspectRatio.Size = new System.Drawing.Size(198, 18); + this.chbMaintainAspectRatio.Size = new System.Drawing.Size(218, 18); this.chbMaintainAspectRatio.TabIndex = 0; this.chbMaintainAspectRatio.Text = "chbMaintainAspectRatio"; this.chbMaintainAspectRatio.UseVisualStyleBackColor = true; @@ -98,7 +98,7 @@ private void InitializeComponent() this.lblScalingMode.Location = new System.Drawing.Point(3, 108); this.lblScalingMode.Name = "lblScalingMode"; this.lblScalingMode.Size = new System.Drawing.Size(94, 29); - this.lblScalingMode.TabIndex = 5; + this.lblScalingMode.TabIndex = 9; this.lblScalingMode.Text = "lblScalingMode"; this.lblScalingMode.TextAlign = System.Drawing.ContentAlignment.MiddleRight; // @@ -107,22 +107,20 @@ private void InitializeComponent() this.pnlHeightPx.Controls.Add(this.lblHeightPx); this.pnlHeightPx.Controls.Add(this.txtHeightPx); this.pnlHeightPx.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlHeightPx.Location = new System.Drawing.Point(205, 84); + this.pnlHeightPx.Location = new System.Drawing.Point(215, 84); this.pnlHeightPx.Name = "pnlHeightPx"; - this.pnlHeightPx.Size = new System.Drawing.Size(96, 21); - this.pnlHeightPx.TabIndex = 7; + this.pnlHeightPx.Size = new System.Drawing.Size(106, 21); + this.pnlHeightPx.TabIndex = 8; // // lblHeightPx // - this.lblHeightPx.AutoSize = true; this.lblHeightPx.Dock = System.Windows.Forms.DockStyle.Fill; this.lblHeightPx.Location = new System.Drawing.Point(62, 0); this.lblHeightPx.Name = "lblHeightPx"; this.lblHeightPx.Padding = new System.Windows.Forms.Padding(0, 3, 0, 0); - this.lblHeightPx.Size = new System.Drawing.Size(60, 16); + this.lblHeightPx.Size = new System.Drawing.Size(44, 21); this.lblHeightPx.TabIndex = 1; this.lblHeightPx.Text = "lblHeightPx"; - this.lblHeightPx.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // // txtHeightPx // @@ -140,20 +138,18 @@ private void InitializeComponent() this.pnlHeightPercent.Dock = System.Windows.Forms.DockStyle.Fill; this.pnlHeightPercent.Location = new System.Drawing.Point(103, 84); this.pnlHeightPercent.Name = "pnlHeightPercent"; - this.pnlHeightPercent.Size = new System.Drawing.Size(96, 21); - this.pnlHeightPercent.TabIndex = 6; + this.pnlHeightPercent.Size = new System.Drawing.Size(106, 21); + this.pnlHeightPercent.TabIndex = 7; // // lblHeightPercent // - this.lblHeightPercent.AutoSize = true; this.lblHeightPercent.Dock = System.Windows.Forms.DockStyle.Fill; this.lblHeightPercent.Location = new System.Drawing.Point(62, 0); this.lblHeightPercent.Name = "lblHeightPercent"; this.lblHeightPercent.Padding = new System.Windows.Forms.Padding(0, 3, 0, 0); - this.lblHeightPercent.Size = new System.Drawing.Size(85, 16); + this.lblHeightPercent.Size = new System.Drawing.Size(44, 21); this.lblHeightPercent.TabIndex = 1; this.lblHeightPercent.Text = "lblHeightPercent"; - this.lblHeightPercent.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // // txtHeightPercent // @@ -169,22 +165,20 @@ private void InitializeComponent() this.pnlWidthPx.Controls.Add(this.lblWidthPx); this.pnlWidthPx.Controls.Add(this.txtWidthPx); this.pnlWidthPx.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlWidthPx.Location = new System.Drawing.Point(205, 57); + this.pnlWidthPx.Location = new System.Drawing.Point(215, 57); this.pnlWidthPx.Name = "pnlWidthPx"; - this.pnlWidthPx.Size = new System.Drawing.Size(96, 21); - this.pnlWidthPx.TabIndex = 4; + this.pnlWidthPx.Size = new System.Drawing.Size(106, 21); + this.pnlWidthPx.TabIndex = 5; // // lblWidthPx // - this.lblWidthPx.AutoSize = true; this.lblWidthPx.Dock = System.Windows.Forms.DockStyle.Fill; this.lblWidthPx.Location = new System.Drawing.Point(62, 0); this.lblWidthPx.Name = "lblWidthPx"; this.lblWidthPx.Padding = new System.Windows.Forms.Padding(0, 3, 0, 0); - this.lblWidthPx.Size = new System.Drawing.Size(57, 16); + this.lblWidthPx.Size = new System.Drawing.Size(44, 21); this.lblWidthPx.TabIndex = 1; this.lblWidthPx.Text = "lblWidthPx"; - this.lblWidthPx.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // // txtWidthPx // @@ -197,12 +191,11 @@ private void InitializeComponent() // // rbByPixels // - this.rbByPixels.AutoSize = true; - this.rbByPixels.Dock = System.Windows.Forms.DockStyle.Top; + this.rbByPixels.Dock = System.Windows.Forms.DockStyle.Fill; this.rbByPixels.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.rbByPixels.Location = new System.Drawing.Point(205, 30); + this.rbByPixels.Location = new System.Drawing.Point(215, 30); this.rbByPixels.Name = "rbByPixels"; - this.rbByPixels.Size = new System.Drawing.Size(96, 18); + this.rbByPixels.Size = new System.Drawing.Size(106, 21); this.rbByPixels.TabIndex = 2; this.rbByPixels.TabStop = true; this.rbByPixels.Text = "rbByPixels"; @@ -210,12 +203,11 @@ private void InitializeComponent() // // rbByPercentage // - this.rbByPercentage.AutoSize = true; - this.rbByPercentage.Dock = System.Windows.Forms.DockStyle.Top; + this.rbByPercentage.Dock = System.Windows.Forms.DockStyle.Fill; this.rbByPercentage.FlatStyle = System.Windows.Forms.FlatStyle.System; this.rbByPercentage.Location = new System.Drawing.Point(103, 30); this.rbByPercentage.Name = "rbByPercentage"; - this.rbByPercentage.Size = new System.Drawing.Size(96, 18); + this.rbByPercentage.Size = new System.Drawing.Size(106, 21); this.rbByPercentage.TabIndex = 1; this.rbByPercentage.TabStop = true; this.rbByPercentage.Text = "rbByPercentage"; @@ -228,20 +220,18 @@ private void InitializeComponent() this.pnlWidthPercent.Dock = System.Windows.Forms.DockStyle.Fill; this.pnlWidthPercent.Location = new System.Drawing.Point(103, 57); this.pnlWidthPercent.Name = "pnlWidthPercent"; - this.pnlWidthPercent.Size = new System.Drawing.Size(96, 21); - this.pnlWidthPercent.TabIndex = 3; + this.pnlWidthPercent.Size = new System.Drawing.Size(106, 21); + this.pnlWidthPercent.TabIndex = 4; // // lblWidthPercent // - this.lblWidthPercent.AutoSize = true; this.lblWidthPercent.Dock = System.Windows.Forms.DockStyle.Fill; this.lblWidthPercent.Location = new System.Drawing.Point(62, 0); this.lblWidthPercent.Name = "lblWidthPercent"; this.lblWidthPercent.Padding = new System.Windows.Forms.Padding(0, 3, 0, 0); - this.lblWidthPercent.Size = new System.Drawing.Size(82, 16); + this.lblWidthPercent.Size = new System.Drawing.Size(44, 21); this.lblWidthPercent.TabIndex = 1; this.lblWidthPercent.Text = "lblWidthPercent"; - this.lblWidthPercent.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // // txtWidthPercent // @@ -270,7 +260,7 @@ private void InitializeComponent() this.lblHeight.Location = new System.Drawing.Point(3, 81); this.lblHeight.Name = "lblHeight"; this.lblHeight.Size = new System.Drawing.Size(94, 27); - this.lblHeight.TabIndex = 4; + this.lblHeight.TabIndex = 6; this.lblHeight.Text = "lblHeight"; this.lblHeight.TextAlign = System.Drawing.ContentAlignment.MiddleRight; // @@ -283,15 +273,15 @@ private void InitializeComponent() this.cmbScalingMode.FormattingEnabled = true; this.cmbScalingMode.Location = new System.Drawing.Point(103, 111); this.cmbScalingMode.Name = "cmbScalingMode"; - this.cmbScalingMode.Size = new System.Drawing.Size(198, 21); - this.cmbScalingMode.TabIndex = 6; + this.cmbScalingMode.Size = new System.Drawing.Size(218, 21); + this.cmbScalingMode.TabIndex = 10; // // ResizeBitmapForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(314, 291); - this.MinimumSize = new System.Drawing.Size(330, 330); + this.ClientSize = new System.Drawing.Size(334, 291); + this.MinimumSize = new System.Drawing.Size(350, 330); this.Name = "ResizeBitmapForm"; this.Text = "ResizeBitmapForm"; this.pnlSettings.ResumeLayout(false); @@ -315,18 +305,18 @@ private void InitializeComponent() private System.Windows.Forms.TableLayoutPanel tblNewSize; private System.Windows.Forms.RadioButton rbByPixels; private System.Windows.Forms.RadioButton rbByPercentage; - private System.Windows.Forms.Panel pnlWidthPercent; + private Controls.AutoMirrorPanel pnlWidthPercent; private System.Windows.Forms.Label lblWidthPercent; private System.Windows.Forms.TextBox txtWidthPercent; private System.Windows.Forms.Label lblWidth; private System.Windows.Forms.Label lblHeight; - private System.Windows.Forms.Panel pnlHeightPx; + private Controls.AutoMirrorPanel pnlHeightPx; private System.Windows.Forms.Label lblHeightPx; private System.Windows.Forms.TextBox txtHeightPx; - private System.Windows.Forms.Panel pnlHeightPercent; + private Controls.AutoMirrorPanel pnlHeightPercent; private System.Windows.Forms.Label lblHeightPercent; private System.Windows.Forms.TextBox txtHeightPercent; - private System.Windows.Forms.Panel pnlWidthPx; + private Controls.AutoMirrorPanel pnlWidthPx; private System.Windows.Forms.Label lblWidthPx; private System.Windows.Forms.TextBox txtWidthPx; private System.Windows.Forms.Label lblScalingMode; From 34b30e698e3c6cb68ec19f4c5a8a8903a880bc0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 10 Jun 2021 17:00:17 +0200 Subject: [PATCH 102/211] Fixing ErrorProvider scaling in .NET 3.5 --- KGySoft.Drawing.ImagingTools/View/Images.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Images.cs b/KGySoft.Drawing.ImagingTools/View/Images.cs index 456fe9d..f48bb36 100644 --- a/KGySoft.Drawing.ImagingTools/View/Images.cs +++ b/KGySoft.Drawing.ImagingTools/View/Images.cs @@ -111,10 +111,15 @@ internal static Icon ToScaledIcon(this Icon icon, bool legacyScaling = true) Size size = referenceSize.Scale(OSUtils.SystemScale); Icon result = icon.ExtractNearestIcon(size, PixelFormat.Format32bppArgb); int mod; - if (!legacyScaling || OSUtils.IsWindows8OrLater || !OSUtils.IsWindows || (mod = result.Width & 0xF) == 0) + if (!legacyScaling || !OSUtils.IsWindows || (mod = result.Width & 0xF) == 0) return result; - // Windows XP-Windows 7 with legacy scaling: we need to make sure that icon size is dividable by 16 +#if !NET35 + if (OSUtils.IsWindows8OrLater) + return result; +#endif + + // .NET 3.5 or Windows XP-Windows 7 with legacy scaling: we need to make sure that icon size is dividable by 16 // so it will not be corrupted (eg. ErrorProvider) using Bitmap iconImage = icon.ExtractBitmap(result.Size)!; result.Dispose(); From c7dce06a4c097a95c8db682c9257f58f5078f34a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 10 Jun 2021 19:04:04 +0200 Subject: [PATCH 103/211] Fixing high DPI appearance --- .../View/Controls/CheckGroupBox.cs | 14 +---- .../View/Forms/EditResourcesForm.Designer.cs | 8 +-- .../View/Forms/EditResourcesForm.cs | 1 - .../Forms/LanguageSettingsForm.Designer.cs | 1 - .../View/Forms/ResizeBitmapForm.Designer.cs | 61 ++++++++++--------- 5 files changed, 39 insertions(+), 46 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs index b4aacb5..7ab09b8 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs @@ -19,6 +19,7 @@ using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Drawing; using System.Windows.Forms; using System.Windows.Forms.VisualStyles; @@ -127,13 +128,6 @@ protected override void OnControlAdded(ControlEventArgs e) protected virtual void OnCheckedChanged(EventArgs e) => (Events[nameof(CheckedChanged)] as EventHandler)?.Invoke(this, e); - protected override void OnLayout(LayoutEventArgs levent) - { - base.OnLayout(levent); - if (levent.AffectedProperty == nameof(Bounds)) - ResetCheckBoxLocation(); - } - protected override void OnRightToLeftChanged(EventArgs e) { base.OnRightToLeftChanged(e); @@ -173,11 +167,7 @@ private void CheckBox_CheckedChanged(object? sender, EventArgs e) OnCheckedChanged(EventArgs.Empty); } - private void CheckBox_SizeChanged(object sender, EventArgs e) - { - if (RightToLeft == RightToLeft.Yes) - ResetCheckBoxLocation(); - } + private void CheckBox_SizeChanged(object sender, EventArgs e) => ResetCheckBoxLocation(); #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index 769371e..8469858 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -138,9 +138,9 @@ private void InitializeComponent() // txtFilter // this.txtFilter.Dock = System.Windows.Forms.DockStyle.Fill; - this.txtFilter.Location = new System.Drawing.Point(45, 2); + this.txtFilter.Location = new System.Drawing.Point(39, 2); this.txtFilter.Name = "txtFilter"; - this.txtFilter.Size = new System.Drawing.Size(527, 20); + this.txtFilter.Size = new System.Drawing.Size(533, 20); this.txtFilter.TabIndex = 1; // // lblFilter @@ -149,8 +149,8 @@ private void InitializeComponent() this.lblFilter.Dock = System.Windows.Forms.DockStyle.Left; this.lblFilter.Location = new System.Drawing.Point(0, 2); this.lblFilter.Name = "lblFilter"; - this.lblFilter.Padding = new System.Windows.Forms.Padding(3); - this.lblFilter.Size = new System.Drawing.Size(45, 19); + this.lblFilter.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0); + this.lblFilter.Size = new System.Drawing.Size(39, 20); this.lblFilter.TabIndex = 0; this.lblFilter.Text = "lblFilter"; // diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs index 201f583..ba28154 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs @@ -123,7 +123,6 @@ private void InitCommandBindings() // View commands CommandBindings.Add(OnCurrentItemChangedCommand) .AddSource(bindingSource, nameof(bindingSource.CurrentItemChanged)); - } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs index 7c45cac..417ec38 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.Designer.cs @@ -178,7 +178,6 @@ private void InitializeComponent() this.tableLayoutPanel1.ResumeLayout(false); this.tableLayoutPanel1.PerformLayout(); this.ResumeLayout(false); - } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs index 7c297bf..dfed35c 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs @@ -93,23 +93,24 @@ private void InitializeComponent() // // lblScalingMode // - this.lblScalingMode.AutoSize = true; this.lblScalingMode.Dock = System.Windows.Forms.DockStyle.Fill; this.lblScalingMode.Location = new System.Drawing.Point(3, 108); this.lblScalingMode.Name = "lblScalingMode"; + this.lblScalingMode.Padding = new System.Windows.Forms.Padding(0, 3, 0, 0); this.lblScalingMode.Size = new System.Drawing.Size(94, 29); this.lblScalingMode.TabIndex = 9; this.lblScalingMode.Text = "lblScalingMode"; - this.lblScalingMode.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + this.lblScalingMode.TextAlign = System.Drawing.ContentAlignment.TopRight; // // pnlHeightPx // this.pnlHeightPx.Controls.Add(this.lblHeightPx); this.pnlHeightPx.Controls.Add(this.txtHeightPx); - this.pnlHeightPx.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlHeightPx.Location = new System.Drawing.Point(215, 84); + this.pnlHeightPx.Dock = System.Windows.Forms.DockStyle.Top; + this.pnlHeightPx.Location = new System.Drawing.Point(212, 81); + this.pnlHeightPx.Margin = new System.Windows.Forms.Padding(0); this.pnlHeightPx.Name = "pnlHeightPx"; - this.pnlHeightPx.Size = new System.Drawing.Size(106, 21); + this.pnlHeightPx.Size = new System.Drawing.Size(112, 21); this.pnlHeightPx.TabIndex = 8; // // lblHeightPx @@ -117,8 +118,8 @@ private void InitializeComponent() this.lblHeightPx.Dock = System.Windows.Forms.DockStyle.Fill; this.lblHeightPx.Location = new System.Drawing.Point(62, 0); this.lblHeightPx.Name = "lblHeightPx"; - this.lblHeightPx.Padding = new System.Windows.Forms.Padding(0, 3, 0, 0); - this.lblHeightPx.Size = new System.Drawing.Size(44, 21); + this.lblHeightPx.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0); + this.lblHeightPx.Size = new System.Drawing.Size(50, 21); this.lblHeightPx.TabIndex = 1; this.lblHeightPx.Text = "lblHeightPx"; // @@ -135,10 +136,11 @@ private void InitializeComponent() // this.pnlHeightPercent.Controls.Add(this.lblHeightPercent); this.pnlHeightPercent.Controls.Add(this.txtHeightPercent); - this.pnlHeightPercent.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlHeightPercent.Location = new System.Drawing.Point(103, 84); + this.pnlHeightPercent.Dock = System.Windows.Forms.DockStyle.Top; + this.pnlHeightPercent.Location = new System.Drawing.Point(100, 81); + this.pnlHeightPercent.Margin = new System.Windows.Forms.Padding(0); this.pnlHeightPercent.Name = "pnlHeightPercent"; - this.pnlHeightPercent.Size = new System.Drawing.Size(106, 21); + this.pnlHeightPercent.Size = new System.Drawing.Size(112, 21); this.pnlHeightPercent.TabIndex = 7; // // lblHeightPercent @@ -146,8 +148,8 @@ private void InitializeComponent() this.lblHeightPercent.Dock = System.Windows.Forms.DockStyle.Fill; this.lblHeightPercent.Location = new System.Drawing.Point(62, 0); this.lblHeightPercent.Name = "lblHeightPercent"; - this.lblHeightPercent.Padding = new System.Windows.Forms.Padding(0, 3, 0, 0); - this.lblHeightPercent.Size = new System.Drawing.Size(44, 21); + this.lblHeightPercent.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0); + this.lblHeightPercent.Size = new System.Drawing.Size(50, 21); this.lblHeightPercent.TabIndex = 1; this.lblHeightPercent.Text = "lblHeightPercent"; // @@ -164,10 +166,11 @@ private void InitializeComponent() // this.pnlWidthPx.Controls.Add(this.lblWidthPx); this.pnlWidthPx.Controls.Add(this.txtWidthPx); - this.pnlWidthPx.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlWidthPx.Location = new System.Drawing.Point(215, 57); + this.pnlWidthPx.Dock = System.Windows.Forms.DockStyle.Top; + this.pnlWidthPx.Location = new System.Drawing.Point(212, 54); + this.pnlWidthPx.Margin = new System.Windows.Forms.Padding(0); this.pnlWidthPx.Name = "pnlWidthPx"; - this.pnlWidthPx.Size = new System.Drawing.Size(106, 21); + this.pnlWidthPx.Size = new System.Drawing.Size(112, 21); this.pnlWidthPx.TabIndex = 5; // // lblWidthPx @@ -175,8 +178,8 @@ private void InitializeComponent() this.lblWidthPx.Dock = System.Windows.Forms.DockStyle.Fill; this.lblWidthPx.Location = new System.Drawing.Point(62, 0); this.lblWidthPx.Name = "lblWidthPx"; - this.lblWidthPx.Padding = new System.Windows.Forms.Padding(0, 3, 0, 0); - this.lblWidthPx.Size = new System.Drawing.Size(44, 21); + this.lblWidthPx.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0); + this.lblWidthPx.Size = new System.Drawing.Size(50, 21); this.lblWidthPx.TabIndex = 1; this.lblWidthPx.Text = "lblWidthPx"; // @@ -217,10 +220,11 @@ private void InitializeComponent() // this.pnlWidthPercent.Controls.Add(this.lblWidthPercent); this.pnlWidthPercent.Controls.Add(this.txtWidthPercent); - this.pnlWidthPercent.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlWidthPercent.Location = new System.Drawing.Point(103, 57); + this.pnlWidthPercent.Dock = System.Windows.Forms.DockStyle.Top; + this.pnlWidthPercent.Location = new System.Drawing.Point(100, 54); + this.pnlWidthPercent.Margin = new System.Windows.Forms.Padding(0); this.pnlWidthPercent.Name = "pnlWidthPercent"; - this.pnlWidthPercent.Size = new System.Drawing.Size(106, 21); + this.pnlWidthPercent.Size = new System.Drawing.Size(112, 21); this.pnlWidthPercent.TabIndex = 4; // // lblWidthPercent @@ -228,8 +232,8 @@ private void InitializeComponent() this.lblWidthPercent.Dock = System.Windows.Forms.DockStyle.Fill; this.lblWidthPercent.Location = new System.Drawing.Point(62, 0); this.lblWidthPercent.Name = "lblWidthPercent"; - this.lblWidthPercent.Padding = new System.Windows.Forms.Padding(0, 3, 0, 0); - this.lblWidthPercent.Size = new System.Drawing.Size(44, 21); + this.lblWidthPercent.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0); + this.lblWidthPercent.Size = new System.Drawing.Size(50, 21); this.lblWidthPercent.TabIndex = 1; this.lblWidthPercent.Text = "lblWidthPercent"; // @@ -244,25 +248,25 @@ private void InitializeComponent() // // lblWidth // - this.lblWidth.AutoSize = true; this.lblWidth.Dock = System.Windows.Forms.DockStyle.Fill; this.lblWidth.Location = new System.Drawing.Point(3, 54); this.lblWidth.Name = "lblWidth"; + this.lblWidth.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0); this.lblWidth.Size = new System.Drawing.Size(94, 27); this.lblWidth.TabIndex = 3; this.lblWidth.Text = "lblWidth"; - this.lblWidth.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + this.lblWidth.TextAlign = System.Drawing.ContentAlignment.TopRight; // // lblHeight // - this.lblHeight.AutoSize = true; this.lblHeight.Dock = System.Windows.Forms.DockStyle.Fill; this.lblHeight.Location = new System.Drawing.Point(3, 81); this.lblHeight.Name = "lblHeight"; + this.lblHeight.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0); this.lblHeight.Size = new System.Drawing.Size(94, 27); this.lblHeight.TabIndex = 6; this.lblHeight.Text = "lblHeight"; - this.lblHeight.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + this.lblHeight.TextAlign = System.Drawing.ContentAlignment.TopRight; // // cmbScalingMode // @@ -271,9 +275,10 @@ private void InitializeComponent() this.cmbScalingMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.cmbScalingMode.FlatStyle = System.Windows.Forms.FlatStyle.System; this.cmbScalingMode.FormattingEnabled = true; - this.cmbScalingMode.Location = new System.Drawing.Point(103, 111); + this.cmbScalingMode.Location = new System.Drawing.Point(100, 108); + this.cmbScalingMode.Margin = new System.Windows.Forms.Padding(0); this.cmbScalingMode.Name = "cmbScalingMode"; - this.cmbScalingMode.Size = new System.Drawing.Size(218, 21); + this.cmbScalingMode.Size = new System.Drawing.Size(224, 21); this.cmbScalingMode.TabIndex = 10; // // ResizeBitmapForm From 9a8664c9cd8b67055b1f96efc504638df25bc0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 10 Jun 2021 19:04:20 +0200 Subject: [PATCH 104/211] Fixing resource text --- .../KGySoft.Drawing.ImagingTools.Messages.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 6b624be..c317143 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -890,7 +890,7 @@ Tip: use '[T]' to filter untranslated texts. Visit Web Site... - + Visit GitHub Project Site... From 574bc36912e9e4e6337200964e403bb20080952a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 10 Jun 2021 19:27:00 +0200 Subject: [PATCH 105/211] AdvancedDataGridView: scaling when using high DPI --- ...ancedPropertyGrid.cs => AdvancedDataGridView.cs} | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) rename KGySoft.Drawing.ImagingTools/View/Controls/{AdvancedPropertyGrid.cs => AdvancedDataGridView.cs} (90%) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedPropertyGrid.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs similarity index 90% rename from KGySoft.Drawing.ImagingTools/View/Controls/AdvancedPropertyGrid.cs rename to KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs index b1d5414..6078c3a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedPropertyGrid.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs @@ -27,7 +27,9 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls { /// - /// Just providing some default style with a few fixed issues + /// Just a DataGridView that + /// - provides some default style with a few fixed issues + /// - scales the columns automatically /// internal class AdvancedDataGridView : DataGridView { @@ -114,6 +116,15 @@ protected override void OnSystemColorsChanged(EventArgs e) : new DataGridViewCellStyle { BackColor = SystemColors.ControlLight, ForeColor = SystemColors.ControlText }; } + protected override void ScaleControl(SizeF factor, BoundsSpecified specified) + { + base.ScaleControl(factor, specified); + if (factor.Width.Equals(1f)) + return; + foreach (DataGridViewColumn column in Columns) + column.Width = (int)(column.Width * factor.Width); + } + #endregion #region Private Methods From 4465abbdb06ee5d8dfc7c2f6b9182458db2ef7e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 11 Jun 2021 14:04:12 +0200 Subject: [PATCH 106/211] EditResources: Fixing the error provider appearance on Linux --- .../View/Forms/EditResourcesForm.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs index ba28154..f16bcbc 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs @@ -40,6 +40,14 @@ internal EditResourcesForm(EditResourcesViewModel viewModel) : base(viewModel) cmbResourceFiles.ValueMember = nameof(KeyValuePair.Key); cmbResourceFiles.DisplayMember = nameof(KeyValuePair.Value); errorProvider.SetIconAlignment(gbTranslatedText, ErrorIconAlignment.MiddleLeft); + + // For Linux/Mono adding an empty column in the middle so the error provider icon will not appear in a new row + if (!OSUtils.IsWindows) + { + pnlEditResourceEntry.ColumnCount = 3; + pnlEditResourceEntry.SetColumn(gbTranslatedText, 2); + pnlEditResourceEntry.ColumnStyles.Insert(1, new ColumnStyle(SizeType.AutoSize)); + } } #endregion @@ -68,8 +76,8 @@ protected override void ApplyResources() protected override void ApplyViewModel() { - InitCommandBindings(); InitPropertyBindings(); + InitCommandBindings(); base.ApplyViewModel(); } From 6167914843f1aafdcdc97e7796a34e1c910c3e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 11 Jun 2021 19:15:40 +0200 Subject: [PATCH 107/211] AdvancedDataGridView: supporting validation levels on cells --- .../View/Controls/AdvancedDataGridView.cs | 194 +++++++++++++++++- KGySoft.Drawing.ImagingTools/View/Images.cs | 33 +-- 2 files changed, 209 insertions(+), 18 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs index 6078c3a..708470b 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs @@ -17,11 +17,16 @@ #region Usings using System; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Drawing; +using System.Linq; using System.Windows.Forms; +using KGySoft.ComponentModel; +using KGySoft.CoreLibraries; + #endregion namespace KGySoft.Drawing.ImagingTools.View.Controls @@ -30,6 +35,7 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls /// Just a DataGridView that /// - provides some default style with a few fixed issues /// - scales the columns automatically + /// - provides scaling error/warning/info icons, which appear also on Linux/Mono /// internal class AdvancedDataGridView : DataGridView { @@ -39,17 +45,24 @@ internal class AdvancedDataGridView : DataGridView private readonly DataGridViewCellStyle defaultColumnHeadersDefaultCellStyle; private readonly DataGridViewCellStyle defaultAlternatingRowsDefaultCellStyle; + private bool isRightToLeft; + private Bitmap? errorIcon; + private Bitmap? warningIcon; + private Bitmap? infoIcon; + #endregion #region Properties + #region Public Properties + // these are reintroduced just for the ShouldSerialize... methods [AmbientValue(null)] [AllowNull] public new DataGridViewCellStyle DefaultCellStyle { get => base.DefaultCellStyle; - set => base.DefaultCellStyle = value; + set => base.DefaultCellStyle = value; } [AmbientValue(null)] @@ -70,6 +83,16 @@ internal class AdvancedDataGridView : DataGridView #endregion + #region Private Properties + + private Bitmap ErrorIcon => errorIcon ??= Icons.SystemError.ToScaledBitmap(this.GetScale()); + private Bitmap WarningIcon => warningIcon ??= Icons.SystemWarning.ToScaledBitmap(this.GetScale()); + private Bitmap InfoIcon => infoIcon ??= Icons.SystemInformation.ToScaledBitmap(this.GetScale()); + + #endregion + + #endregion + #region Constructors public AdvancedDataGridView() @@ -99,8 +122,39 @@ public AdvancedDataGridView() #region Methods + #region Static Methods + + private static string GetRowValidationText(ValidationResultsCollection validationResults) => validationResults.Count switch + { + 0 => String.Empty, + 1 => validationResults[0].Message, + _ => validationResults.Select(r => r.Message).Join(Environment.NewLine) + }; + + private static string GetCellValidationText(ValidationResultsCollection validationResults, string propertyName) + { + int len = validationResults.Count; + if (len == 0) + return String.Empty; + + for (var s = ValidationSeverity.Error; s >= ValidationSeverity.Information; s--) + { + for (int i = 0; i < len; i++) + { + if (validationResults[i].Severity == s && validationResults[i].PropertyName == propertyName) + return validationResults[i].Message; + } + } + + return String.Empty; + } + + #endregion + + #region Instance Methods + #region Protected Methods - + protected override void OnParentChanged(EventArgs e) { base.OnParentChanged(e); @@ -125,10 +179,142 @@ protected override void ScaleControl(SizeF factor, BoundsSpecified specified) column.Width = (int)(column.Width * factor.Width); } + protected override void OnRightToLeftChanged(EventArgs e) + { + base.OnRightToLeftChanged(e); + isRightToLeft = RightToLeft == RightToLeft.Yes; + } + + protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e) + { + e.Paint(e.CellBounds, e.PaintParts & ~DataGridViewPaintParts.ErrorIcon); + if ((e.PaintParts & DataGridViewPaintParts.ErrorIcon) != DataGridViewPaintParts.None) + DrawValidationIcon(e); + + e.Handled = true; + } + + protected override void OnRowErrorTextNeeded(DataGridViewRowErrorTextNeededEventArgs e) + { + if (e.RowIndex < 0 || Rows[e.RowIndex].DataBoundItem is not IValidatingObject validatingObject) + return; + + e.ErrorText = GetRowValidationText(validatingObject.ValidationResults); + } + + protected override void OnCellErrorTextNeeded(DataGridViewCellErrorTextNeededEventArgs e) + { + if (e.RowIndex < 0 || Rows[e.RowIndex].DataBoundItem is not IValidatingObject validatingObject) + return; + + ValidationResultsCollection validationResults = validatingObject.ValidationResults; + e.ErrorText = validationResults.Count == 0 ? String.Empty : GetCellValidationText(validationResults, Columns[e.ColumnIndex].DataPropertyName); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + errorIcon?.Dispose(); + warningIcon?.Dispose(); + infoIcon?.Dispose(); + } + + base.Dispose(disposing); + } + #endregion #region Private Methods - + + private void DrawValidationIcon(DataGridViewCellPaintingEventArgs e) + { + Rectangle bounds = e.CellBounds; + bounds.Height -= 1; + bounds.Width -= 1; + + Bitmap? icon = GetCellIcon(e); + if (icon == null) + return; + + Size size = icon.Size; + Rectangle iconRect = new Rectangle(bounds.Left + (isRightToLeft ? 4 : bounds.Width - size.Width - 4), + bounds.Top + ((bounds.Height >> 1) - (size.Height >> 1)), + size.Width, size.Height); + + Rectangle iconBounds = Rectangle.Intersect(bounds, iconRect); + if (iconBounds.IsEmpty) + return; + + bool clip = iconRect != iconBounds; + if (clip) + e.Graphics.IntersectClip(iconBounds); + e.Graphics.DrawImage(icon, iconRect); + if (clip) + e.Graphics.ResetClip(); + } + + private Bitmap? GetCellIcon(DataGridViewCellPaintingEventArgs e) + { + #region Local Methods + + static bool HasMatch(IList list, string name) + { + // not using LINQ and delegates for better performance + int len = list.Count; + for (int i = 0; i < len; i++) + { + if (list[i].PropertyName == name) + return true; + } + + return false; + } + + #endregion + + if (e.RowIndex < 0) + return null; + + // falling back to default error logic + if (Rows[e.RowIndex].DataBoundItem is not IValidatingObject validatingObject) + return String.IsNullOrEmpty(e.ErrorText) ? null : ErrorIcon; + + if (!OSUtils.IsWindows) + EnsureValidationText(e, validatingObject); + + ValidationResultsCollection validationResults = validatingObject.ValidationResults; + if (validationResults.Count == 0) + return null; + + // row header + if (e.ColumnIndex < 0) + { + return validationResults.HasErrors ? ErrorIcon + : validationResults.HasWarnings ? WarningIcon + : validationResults.HasInfos ? InfoIcon + : null; + } + + // cell + string propertyName = Columns[e.ColumnIndex].DataPropertyName; + return HasMatch(validationResults.Errors, propertyName) ? ErrorIcon + : HasMatch(validationResults.Warnings, propertyName) ? WarningIcon + : HasMatch(validationResults.Infos, propertyName) ? InfoIcon + : null; + } + + private void EnsureValidationText(DataGridViewCellPaintingEventArgs e, IValidatingObject validatingObject) + { + DataGridViewRow row = Rows[e.RowIndex]; + DataGridViewCell cell = e.ColumnIndex < 0 ? row.HeaderCell : row.Cells[e.ColumnIndex]; + ValidationResultsCollection validationResults = validatingObject.ValidationResults; + + cell.ErrorText = e.ColumnIndex < 0 + ? GetRowValidationText(validationResults) + : GetCellValidationText(validationResults, Columns[e.ColumnIndex].DataPropertyName); + } + private void AdjustAlternatingRowsColors() { if (!Equals(AlternatingRowsDefaultCellStyle, defaultAlternatingRowsDefaultCellStyle)) @@ -146,5 +332,7 @@ private void AdjustAlternatingRowsColors() #endregion #endregion + + #endregion } } diff --git a/KGySoft.Drawing.ImagingTools/View/Images.cs b/KGySoft.Drawing.ImagingTools/View/Images.cs index f48bb36..dc45621 100644 --- a/KGySoft.Drawing.ImagingTools/View/Images.cs +++ b/KGySoft.Drawing.ImagingTools/View/Images.cs @@ -95,12 +95,12 @@ internal static class Images #region Internal Methods - internal static Bitmap ToScaledBitmap(this Icon icon) + internal static Bitmap ToScaledBitmap(this Icon icon, PointF? scale = null) { if (icon == null) throw new ArgumentNullException(nameof(icon), PublicResources.ArgumentNull); using (icon) - return icon.ExtractNearestBitmap(referenceSize.Scale(OSUtils.SystemScale), PixelFormat.Format32bppArgb); + return icon.ExtractNearestBitmap(referenceSize.Scale(scale ?? OSUtils.SystemScale), PixelFormat.Format32bppArgb); } internal static Icon ToScaledIcon(this Icon icon, bool legacyScaling = true) @@ -108,24 +108,27 @@ internal static Icon ToScaledIcon(this Icon icon, bool legacyScaling = true) if (icon == null) throw new ArgumentNullException(nameof(icon), PublicResources.ArgumentNull); - Size size = referenceSize.Scale(OSUtils.SystemScale); - Icon result = icon.ExtractNearestIcon(size, PixelFormat.Format32bppArgb); - int mod; - if (!legacyScaling || !OSUtils.IsWindows || (mod = result.Width & 0xF) == 0) - return result; + using (icon) + { + Size size = referenceSize.Scale(OSUtils.SystemScale); + Icon result = icon.ExtractNearestIcon(size, PixelFormat.Format32bppArgb); + int mod; + if (!legacyScaling || !OSUtils.IsWindows || (mod = result.Width & 0xF) == 0) + return result; #if !NET35 - if (OSUtils.IsWindows8OrLater) - return result; + if (OSUtils.IsWindows8OrLater) + return result; #endif - // .NET 3.5 or Windows XP-Windows 7 with legacy scaling: we need to make sure that icon size is dividable by 16 - // so it will not be corrupted (eg. ErrorProvider) - using Bitmap iconImage = icon.ExtractBitmap(result.Size)!; - result.Dispose(); + // .NET 3.5 or Windows XP-Windows 7 with legacy scaling: we need to make sure that icon size is dividable by 16 + // so it will not be corrupted (eg. ErrorProvider) + using Bitmap iconImage = icon.ExtractBitmap(result.Size)!; + result.Dispose(); - // returning a larger icon without scaling so apparently it will have the same size as the original one - return iconImage.ToIcon(size.Width + (16 - mod), ScalingMode.NoScaling); + // returning a larger icon without scaling so apparently it will have the same size as the original one + return iconImage.ToIcon(size.Width + (16 - mod), ScalingMode.NoScaling); + } } #endregion From 8d363871e413ae4deff482389caa602ddb0e4221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 12 Jun 2021 14:38:51 +0200 Subject: [PATCH 108/211] Movinf Error/Warning/InfoProvider down to MvvmBaseForm, adjusting validations --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 2 +- .../Model/ResourceEntry.cs | 2 +- KGySoft.Drawing.ImagingTools/Res.cs | 2 +- .../View/Controls/CheckGroupBox.cs | 3 +- .../View/Forms/EditResourcesForm.Designer.cs | 8 --- .../View/Forms/EditResourcesForm.cs | 31 ++--------- .../View/Forms/MvvmBaseForm.cs | 51 +++++++++++++++++ .../View/Forms/ResizeBitmapForm.Designer.cs | 11 ++-- .../Forms/TransformBitmapFormBase.Designer.cs | 24 -------- .../View/Forms/TransformBitmapFormBase.cs | 55 ++----------------- KGySoft.Drawing.ImagingTools/View/Images.cs | 6 +- .../ViewModel/TransformBitmapViewModelBase.cs | 7 +-- 12 files changed, 79 insertions(+), 123 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index c317143..7e32024 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -333,7 +333,7 @@ Brightness: {5:F0}% Resource format string is invalid. - One or more placeholder is missing from the translated resource format string. + One or more placeholders are missing from the translated resource format string. Could not save image due to an error: {0} diff --git a/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs b/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs index f325b7a..7e230ff 100644 --- a/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs +++ b/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs @@ -267,7 +267,7 @@ bool HandlePlaceholder(ValidationResultsCollection validationResults, int index, if (state != State.Text) AddFormatError(result); else if (usedPlaceholders != (1 << placeholderCount) - 1) - result.AddError(nameof(TranslatedText), Res.ErrorMessageResourcePlaceholderUnusedIndices); + result.AddWarning(nameof(TranslatedText), Res.ErrorMessageResourcePlaceholderUnusedIndices); return result; } diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index ffb2d9e..e90613c 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -190,7 +190,7 @@ internal static class Res /// Resource format string is invalid. internal static string ErrorMessageResourceFormatError => Get("ErrorMessage_ResourceFormatError"); - /// One or more placeholder is missing from the translated resource format string. + /// One or more placeholders are missing from the translated resource format string. internal static string ErrorMessageResourcePlaceholderUnusedIndices => Get("ErrorMessage_ResourcePlaceholderUnusedIndices"); /// The selected quantizer supports partial transparency, which is not supported by ditherers, diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs index 7ab09b8..2649cde 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs @@ -19,7 +19,6 @@ using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; -using System.Drawing; using System.Windows.Forms; using System.Windows.Forms.VisualStyles; @@ -167,7 +166,7 @@ private void CheckBox_CheckedChanged(object? sender, EventArgs e) OnCheckedChanged(EventArgs.Empty); } - private void CheckBox_SizeChanged(object sender, EventArgs e) => ResetCheckBoxLocation(); + private void CheckBox_SizeChanged(object? sender, EventArgs e) => ResetCheckBoxLocation(); #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index 8469858..b9ee1c8 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -49,7 +49,6 @@ private void InitializeComponent() this.gbTranslatedText = new System.Windows.Forms.GroupBox(); this.txtTranslatedText = new System.Windows.Forms.TextBox(); this.okCancelApplyButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); - this.errorProvider = new System.Windows.Forms.ErrorProvider(this.components); this.gbResourceEntries.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.gridResources)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.bindingSource)).BeginInit(); @@ -58,7 +57,6 @@ private void InitializeComponent() this.pnlEditResourceEntry.SuspendLayout(); this.gbOriginalText.SuspendLayout(); this.gbTranslatedText.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.errorProvider)).BeginInit(); this.SuspendLayout(); // // gbResourceEntries @@ -257,10 +255,6 @@ private void InitializeComponent() this.okCancelApplyButtons.Size = new System.Drawing.Size(578, 35); this.okCancelApplyButtons.TabIndex = 1; // - // errorProvider - // - this.errorProvider.ContainerControl = this; - // // EditResourcesForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -287,7 +281,6 @@ private void InitializeComponent() this.gbOriginalText.PerformLayout(); this.gbTranslatedText.ResumeLayout(false); this.gbTranslatedText.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.errorProvider)).EndInit(); this.ResumeLayout(false); } @@ -312,6 +305,5 @@ private void InitializeComponent() private Controls.AutoMirrorPanel pnlFilter; private System.Windows.Forms.TextBox txtFilter; private System.Windows.Forms.Label lblFilter; - private System.Windows.Forms.ErrorProvider errorProvider; } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs index f16bcbc..d1dc329 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs @@ -17,7 +17,6 @@ #region Usings using System.Collections.Generic; -using System.Linq; using System.Windows.Forms; using KGySoft.Drawing.ImagingTools.Model; @@ -39,7 +38,9 @@ internal EditResourcesForm(EditResourcesViewModel viewModel) : base(viewModel) InitializeComponent(); cmbResourceFiles.ValueMember = nameof(KeyValuePair.Key); cmbResourceFiles.DisplayMember = nameof(KeyValuePair.Value); - errorProvider.SetIconAlignment(gbTranslatedText, ErrorIconAlignment.MiddleLeft); + ErrorProvider.SetIconAlignment(gbTranslatedText, ErrorIconAlignment.MiddleLeft); + WarningProvider.SetIconAlignment(gbTranslatedText, ErrorIconAlignment.MiddleLeft); + ValidationMapping[nameof(ResourceEntry.TranslatedText)] = gbTranslatedText; // For Linux/Mono adding an empty column in the middle so the error provider icon will not appear in a new row if (!OSUtils.IsWindows) @@ -71,7 +72,6 @@ protected override void ApplyResources() { base.ApplyResources(); Icon = Properties.Resources.Language; - errorProvider.Icon = Icons.SystemError.ToScaledIcon(); } protected override void ApplyViewModel() @@ -107,10 +107,6 @@ private void InitPropertyBindings() // bindingSource.TranslatedText <-> txtTranslatedText.Text txtTranslatedText.DataBindings.Add(nameof(txtTranslatedText.Text), bindingSource, nameof(ResourceEntry.TranslatedText), false, DataSourceUpdateMode.OnValidation); - - // this.RightToLeft -> errorProvider.RightToLeft - CommandBindings.AddPropertyBinding(this, nameof(RightToLeft), nameof(ErrorProvider.RightToLeft), - rtl => rtl is RightToLeft.Yes, errorProvider); } private void InitCommandBindings() @@ -129,24 +125,9 @@ private void InitCommandBindings() .AddSource(okCancelApplyButtons.CancelButton, nameof(okCancelApplyButtons.CancelButton.Click)); // View commands - CommandBindings.Add(OnCurrentItemChangedCommand) - .AddSource(bindingSource, nameof(bindingSource.CurrentItemChanged)); - } - - #endregion - - #region Command Handlers - - private void OnCurrentItemChangedCommand() - { - var current = bindingSource.Current as ResourceEntry; - if (current == null) - { - errorProvider.SetError(gbTranslatedText, null); - return; - } - - errorProvider.SetError(gbTranslatedText, current.ValidationResults.Errors.FirstOrDefault()?.Message); + CommandBindings.Add(ValidationResultsChangedCommand) + .AddSource(bindingSource, nameof(bindingSource.CurrentItemChanged)) + .WithParameter(() => (bindingSource.Current as ResourceEntry)?.ValidationResults); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index 18aa7e1..61090a7 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -17,7 +17,9 @@ #region Usings using System; +using System.Collections.Generic; using System.Drawing; +using System.Linq; using System.Threading; using System.Windows.Forms; @@ -36,6 +38,11 @@ internal partial class MvvmBaseForm : BaseForm, IView private readonly int threadId; private readonly ManualResetEventSlim handleCreated; + private ErrorProvider? warningProvider; + private ErrorProvider? infoProvider; + private ErrorProvider? errorProvider; + private ICommand? validationResultsChangesCommand; + private bool isClosing; private bool isLoaded; private bool isRtlChanging; @@ -50,6 +57,13 @@ internal partial class MvvmBaseForm : BaseForm, IView protected TViewModel ViewModel { get; } = default!; protected CommandBindingsCollection CommandBindings { get; } = new WinFormsCommandBindingsCollection(); + protected ErrorProvider ErrorProvider => errorProvider ??= CreateProvider(ValidationSeverity.Error); + protected ErrorProvider WarningProvider => warningProvider ??= CreateProvider(ValidationSeverity.Warning); + protected ErrorProvider InfoProvider => infoProvider ??= CreateProvider(ValidationSeverity.Information); + + protected Dictionary ValidationMapping { get; } = new Dictionary(); + protected ICommand ValidationResultsChangedCommand => validationResultsChangesCommand ??= new SimpleCommand(OnValidationResultsChangedCommand); + #endregion #region Private Properties @@ -133,6 +147,7 @@ protected override void OnLoad(EventArgs e) protected virtual void ApplyViewModel() { + InitPropertyBindings(); InitCommandBindings(); VM.ViewLoaded(); } @@ -175,12 +190,34 @@ protected override void Dispose(bool disposing) #region Private Methods + private void InitPropertyBindings() + { + if (ValidationMapping.Count != 0) + { + // this.RightToLeft -> errorProvider/warningProvider/infoProvider.RightToLeft + CommandBindings.AddPropertyBinding(this, nameof(RightToLeft), nameof(ErrorProvider.RightToLeft), + rtl => rtl is RightToLeft.Yes, ErrorProvider, WarningProvider, InfoProvider); + } + } + private void InitCommandBindings() { CommandBindings.Add(OnDisplayLanguageChangedCommand) .AddSource(typeof(LanguageSettings), nameof(LanguageSettings.DisplayLanguageChanged)); } + private ErrorProvider CreateProvider(ValidationSeverity level) => new ErrorProvider(components) + { + ContainerControl = this, + Icon = level switch + { + ValidationSeverity.Error => Icons.SystemError.ToScaledIcon(this.GetScale()), + ValidationSeverity.Warning => Icons.SystemWarning.ToScaledIcon(this.GetScale()), + ValidationSeverity.Information => Icons.SystemInformation.ToScaledIcon(this.GetScale()), + _ => null + } + }; + private void ShowChildView(IViewModel vm) => ViewFactory.ShowDialog(vm, this); private void InvokeIfRequired(Action action) @@ -230,6 +267,20 @@ private void OnDisplayLanguageChangedCommand() ApplyStringResources(); } + private void OnValidationResultsChangedCommand(ValidationResultsCollection validationResults) + { + foreach (KeyValuePair mapping in ValidationMapping) + { + var propertyResults = validationResults[mapping.Key]; // var is IList in .NET 3.5 and IReadOnlyList above + ValidationResult? error = propertyResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Error); + ValidationResult? warning = error == null ? propertyResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Warning) : null; + ValidationResult? info = error == null && warning == null ? propertyResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Information) : null; + ErrorProvider.SetError(mapping.Value, error?.Message); + WarningProvider.SetError(mapping.Value, warning?.Message); + InfoProvider.SetError(mapping.Value, info?.Message); + } + } + #endregion #region Explicit Interface Implementations diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs index dfed35c..b2bf727 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs @@ -145,11 +145,12 @@ private void InitializeComponent() // // lblHeightPercent // - this.lblHeightPercent.Dock = System.Windows.Forms.DockStyle.Fill; + this.lblHeightPercent.AutoSize = true; + this.lblHeightPercent.Dock = System.Windows.Forms.DockStyle.Left; this.lblHeightPercent.Location = new System.Drawing.Point(62, 0); this.lblHeightPercent.Name = "lblHeightPercent"; this.lblHeightPercent.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0); - this.lblHeightPercent.Size = new System.Drawing.Size(50, 21); + this.lblHeightPercent.Size = new System.Drawing.Size(85, 15); this.lblHeightPercent.TabIndex = 1; this.lblHeightPercent.Text = "lblHeightPercent"; // @@ -229,11 +230,12 @@ private void InitializeComponent() // // lblWidthPercent // - this.lblWidthPercent.Dock = System.Windows.Forms.DockStyle.Fill; + this.lblWidthPercent.AutoSize = true; + this.lblWidthPercent.Dock = System.Windows.Forms.DockStyle.Left; this.lblWidthPercent.Location = new System.Drawing.Point(62, 0); this.lblWidthPercent.Name = "lblWidthPercent"; this.lblWidthPercent.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0); - this.lblWidthPercent.Size = new System.Drawing.Size(50, 21); + this.lblWidthPercent.Size = new System.Drawing.Size(82, 15); this.lblWidthPercent.TabIndex = 1; this.lblWidthPercent.Text = "lblWidthPercent"; // @@ -286,6 +288,7 @@ private void InitializeComponent() this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(334, 291); + this.Location = new System.Drawing.Point(0, 0); this.MinimumSize = new System.Drawing.Size(350, 330); this.Name = "ResizeBitmapForm"; this.Text = "ResizeBitmapForm"; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs index df3d047..fe2fa62 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs @@ -20,12 +20,6 @@ private void InitializeComponent() this.okCancelButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); this.previewImage = new KGySoft.Drawing.ImagingTools.View.UserControls.PreviewImageControl(); this.pnlSettings = new Controls.AutoMirrorPanel(); - this.errorProvider = new System.Windows.Forms.ErrorProvider(this.components); - this.warningProvider = new System.Windows.Forms.ErrorProvider(this.components); - this.infoProvider = new System.Windows.Forms.ErrorProvider(this.components); - ((System.ComponentModel.ISupportInitialize)(this.errorProvider)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.warningProvider)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.infoProvider)).BeginInit(); this.SuspendLayout(); // // progress @@ -62,18 +56,6 @@ private void InitializeComponent() this.pnlSettings.Size = new System.Drawing.Size(248, 56); this.pnlSettings.TabIndex = 0; // - // errorProvider - // - this.errorProvider.ContainerControl = this; - // - // warningProvider - // - this.warningProvider.ContainerControl = this; - // - // infoProvider - // - this.infoProvider.ContainerControl = this; - // // TransformBitmapFormBase // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -86,9 +68,6 @@ private void InitializeComponent() this.MinimizeBox = false; this.Name = "TransformBitmapFormBase"; this.Text = "TransformBitmapFormBase"; - ((System.ComponentModel.ISupportInitialize)(this.errorProvider)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.warningProvider)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.infoProvider)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); @@ -100,8 +79,5 @@ private void InitializeComponent() private UserControls.OkCancelButtons okCancelButtons; private UserControls.PreviewImageControl previewImage; protected Controls.AutoMirrorPanel pnlSettings; - private System.Windows.Forms.ErrorProvider warningProvider; - private System.Windows.Forms.ErrorProvider infoProvider; - private System.Windows.Forms.ErrorProvider errorProvider; } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs index 479c47d..13607c3 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs @@ -16,12 +16,8 @@ #region Usings -using System.Collections.Generic; -using System.Linq; using System.Windows.Forms; -using KGySoft.ComponentModel; -using KGySoft.CoreLibraries; using KGySoft.Drawing.ImagingTools.ViewModel; #endregion @@ -30,15 +26,6 @@ namespace KGySoft.Drawing.ImagingTools.View.Forms { internal partial class TransformBitmapFormBase : MvvmBaseForm { - #region Properties - - protected Dictionary ValidationMapping { get; } - protected ErrorProvider ErrorProvider => errorProvider; - protected ErrorProvider WarningProvider => warningProvider; - protected ErrorProvider InfoProvider => infoProvider; - - #endregion - #region Constructors #region Protected Constructors @@ -50,11 +37,8 @@ protected TransformBitmapFormBase(TransformBitmapViewModelBase viewModel) AcceptButton = okCancelButtons.OKButton; CancelButton = okCancelButtons.CancelButton; - errorProvider.SetIconAlignment(previewImage.ImageViewer, ErrorIconAlignment.MiddleLeft); - ValidationMapping = new Dictionary - { - [nameof(viewModel.PreviewImageViewModel.PreviewImage)] = previewImage.ImageViewer, - }; + ErrorProvider.SetIconAlignment(previewImage.ImageViewer, ErrorIconAlignment.MiddleLeft); + ValidationMapping[nameof(viewModel.PreviewImageViewModel.PreviewImage)] = previewImage.ImageViewer; } #endregion @@ -74,14 +58,6 @@ private TransformBitmapFormBase() : this(null!) #region Protected Methods - protected override void ApplyResources() - { - base.ApplyResources(); - errorProvider.Icon = Icons.SystemError.ToScaledIcon(); - warningProvider.Icon = Icons.SystemWarning.ToScaledIcon(); - infoProvider.Icon = Icons.SystemInformation.ToScaledIcon(); - } - protected override void ApplyViewModel() { InitCommandBindings(); @@ -129,8 +105,9 @@ private void InitCommandBindings() .AddSource(okCancelButtons.CancelButton, nameof(okCancelButtons.CancelButton.Click)); // View commands - CommandBindings.Add>(OnValidationResultsChangedCommand) - .AddSource(ViewModel, nameof(ViewModel.ValidationResultsChanged)); + CommandBindings.Add(ValidationResultsChangedCommand) + .AddSource(ViewModel, nameof(ViewModel.ValidationResultsChanged)) + .WithParameter(() => ViewModel.ValidationResults); } private void InitPropertyBindings() @@ -143,28 +120,6 @@ private void InitPropertyBindings() // VM.Progress -> progress.Progress CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Progress), nameof(progress.Progress), progress); - - // this.RightToLeft -> errorProvider/warningProvider/infoProvider.RightToLeft - CommandBindings.AddPropertyBinding(this, nameof(RightToLeft), nameof(ErrorProvider.RightToLeft), - rtl => rtl is RightToLeft.Yes, errorProvider, warningProvider, infoProvider); - } - - #endregion - - #region Command handlers - - private void OnValidationResultsChangedCommand(ICommandSource> src) - { - foreach (KeyValuePair mapping in ValidationMapping) - { - var validationResults = src.EventArgs.EventData[mapping.Key]; - ValidationResult? error = validationResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Error); - ValidationResult? warning = error == null ? validationResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Warning) : null; - ValidationResult? info = error == null && warning == null ? validationResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Information) : null; - errorProvider.SetError(mapping.Value, error?.Message); - warningProvider.SetError(mapping.Value, warning?.Message); - infoProvider.SetError(mapping.Value, info?.Message); - } } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Images.cs b/KGySoft.Drawing.ImagingTools/View/Images.cs index dc45621..a40756a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Images.cs +++ b/KGySoft.Drawing.ImagingTools/View/Images.cs @@ -103,17 +103,17 @@ internal static Bitmap ToScaledBitmap(this Icon icon, PointF? scale = null) return icon.ExtractNearestBitmap(referenceSize.Scale(scale ?? OSUtils.SystemScale), PixelFormat.Format32bppArgb); } - internal static Icon ToScaledIcon(this Icon icon, bool legacyScaling = true) + internal static Icon ToScaledIcon(this Icon icon, PointF? scale = null, bool div16Scaling = true) { if (icon == null) throw new ArgumentNullException(nameof(icon), PublicResources.ArgumentNull); using (icon) { - Size size = referenceSize.Scale(OSUtils.SystemScale); + Size size = referenceSize.Scale(scale ?? OSUtils.SystemScale); Icon result = icon.ExtractNearestIcon(size, PixelFormat.Format32bppArgb); int mod; - if (!legacyScaling || !OSUtils.IsWindows || (mod = result.Width & 0xF) == 0) + if (!div16Scaling || !OSUtils.IsWindows || (mod = result.Width & 0xF) == 0) return result; #if !NET35 diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs index d682307..2c99507 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs @@ -23,7 +23,6 @@ using System.Threading; using KGySoft.ComponentModel; -using KGySoft.CoreLibraries; using KGySoft.Drawing.ImagingTools.Model; #endregion @@ -61,7 +60,7 @@ protected abstract class GenerateTaskBase : AsyncTaskBase #region Events - internal event EventHandler>? ValidationResultsChanged + internal event EventHandler? ValidationResultsChanged { add => ValidationResultsChangedHandler += value; remove => ValidationResultsChangedHandler -= value; @@ -102,7 +101,7 @@ internal event EventHandler>? ValidationR #region Private Properties private Exception? GeneratePreviewError { get => Get(); set => Set(value); } - private EventHandler>? ValidationResultsChangedHandler { get => Get>?>(); set => Set(value); } + private EventHandler? ValidationResultsChangedHandler { get => Get(); set => Set(value); } #endregion @@ -144,7 +143,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) case nameof(ValidationResults): var validationResults = (ValidationResultsCollection)e.NewValue!; IsValid = !validationResults.HasErrors; - ValidationResultsChangedHandler?.Invoke(this, new EventArgs(validationResults)); + ValidationResultsChangedHandler?.Invoke(this, EventArgs.Empty); return; case nameof(GeneratePreviewError): From 0459ea14a5e73648c82b9adff32af0e86b979d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 12 Jun 2021 17:45:14 +0200 Subject: [PATCH 109/211] InstallationManager: New ImagingToolsVersion property --- .../_Classes/InstallationManager.cs | 17 +++++++++++++---- .../_Extensions/VersionExtensions.cs | 15 +++++++++++---- changelog.txt | 2 ++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs index 1e34d2c..49fb6f0 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs @@ -18,8 +18,8 @@ using System; using System.IO; -using System.Linq; using System.Security; + using KGySoft.CoreLibraries; using KGySoft.Drawing.ImagingTools.Model; #if NET45 @@ -56,9 +56,7 @@ public static class InstallationManager #endregion - #region Methods - - #region Public Methods + #region Properties /// /// Gets the available debugger visualizer version that can be installed with the class. @@ -66,6 +64,17 @@ public static class InstallationManager /// public static InstallationInfo AvailableVersion => availableVersion ??= new InstallationInfo(Files.GetExecutingPath()); + /// + /// Gets version of the currently used ImagingTools application. + /// + public static Version ImagingToolsVersion { get; } = typeof(Configuration).Assembly.GetName().Version; + + #endregion + + #region Methods + + #region Public Methods + /// /// Gets the installation information of the debugger visualizer for the specified . /// diff --git a/KGySoft.Drawing.ImagingTools/_Extensions/VersionExtensions.cs b/KGySoft.Drawing.ImagingTools/_Extensions/VersionExtensions.cs index f11b9ef..e55912a 100644 --- a/KGySoft.Drawing.ImagingTools/_Extensions/VersionExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/_Extensions/VersionExtensions.cs @@ -26,10 +26,17 @@ internal static class VersionExtensions { #region Methods - internal static Version Normalize(this Version version) - => version.Revision >= 0 ? version - : version.Build >= 0 ? new Version(version.Major, version.Minor, version.Build, 0) - : new Version(version.Major, version.Minor, version.Build, version.Revision); + internal static bool NormalizedEquals(this Version? version, Version? other) + { + if (version is null && other is null) + return true; + if (version is null || other is null) + return false; + return version.Major == other.Major + && version.Minor == other.Minor + && (version.Build == other.Build || version.Build <= 0 && other.Build <= 0) + && (version.Revision == other.Revision || version.Revision <= 0 && other.Revision <= 0); + } #endregion } diff --git a/changelog.txt b/changelog.txt index 9c470f1..895b68d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -33,6 +33,8 @@ - Fixing possible errors when closing forms while an async operation is still in progress. * API changes: * Members are annotated for using C# 8.0 nullable references + + InstallationManager class: + + New ImagingToolsVersion property + ViewModelFactory class: + New CreateLanguageSettings method + New CreateEditResources method From b2c422a7461946a9ba3d968f5dbb465b3fa0a80d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 12 Jun 2021 17:45:42 +0200 Subject: [PATCH 110/211] Disabling multi selection --- .../Forms/DownloadResourcesForm.Designer.cs | 23 ++++++++++--------- .../View/Forms/EditResourcesForm.Designer.cs | 3 ++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs index 7f0862a..88286a8 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs @@ -20,12 +20,12 @@ private void InitializeComponent() this.bindingSource = new System.Windows.Forms.BindingSource(this.components); this.okCancelButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); this.gridDownloadableResources = new KGySoft.Drawing.ImagingTools.View.Controls.AdvancedDataGridView(); - this.progress = new KGySoft.Drawing.ImagingTools.View.Controls.DownloadProgressStatusStrip(); this.colSelected = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.colLanguage = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colAuthor = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colImagingToolsVersion = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colDescription = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.progress = new KGySoft.Drawing.ImagingTools.View.Controls.DownloadProgressStatusStrip(); ((System.ComponentModel.ISupportInitialize)(this.bindingSource)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.gridDownloadableResources)).BeginInit(); this.SuspendLayout(); @@ -56,20 +56,11 @@ private void InitializeComponent() this.gridDownloadableResources.DataSource = this.bindingSource; this.gridDownloadableResources.Dock = System.Windows.Forms.DockStyle.Fill; this.gridDownloadableResources.Location = new System.Drawing.Point(3, 3); + this.gridDownloadableResources.MultiSelect = false; this.gridDownloadableResources.Name = "gridDownloadableResources"; this.gridDownloadableResources.Size = new System.Drawing.Size(358, 163); this.gridDownloadableResources.TabIndex = 0; // - // progress - // - this.progress.BackColor = System.Drawing.Color.Transparent; - this.progress.Location = new System.Drawing.Point(3, 201); - this.progress.Name = "progress"; - this.progress.Size = new System.Drawing.Size(358, 22); - this.progress.SizingGrip = false; - this.progress.TabIndex = 2; - this.progress.Text = "drawingProgressStatusStrip1"; - // // colSelected // this.colSelected.DataPropertyName = "Selected"; @@ -107,6 +98,16 @@ private void InitializeComponent() this.colDescription.ReadOnly = true; this.colDescription.Width = 120; // + // progress + // + this.progress.BackColor = System.Drawing.Color.Transparent; + this.progress.Location = new System.Drawing.Point(3, 201); + this.progress.Name = "progress"; + this.progress.Size = new System.Drawing.Size(358, 22); + this.progress.SizingGrip = false; + this.progress.TabIndex = 2; + this.progress.Text = "drawingProgressStatusStrip1"; + // // DownloadResourcesForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index b9ee1c8..6dc6a1f 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -92,6 +92,7 @@ private void InitializeComponent() this.gridResources.DefaultCellStyle = dataGridViewCellStyle1; this.gridResources.Dock = System.Windows.Forms.DockStyle.Fill; this.gridResources.Location = new System.Drawing.Point(3, 40); + this.gridResources.MultiSelect = false; this.gridResources.Name = "gridResources"; this.gridResources.Size = new System.Drawing.Size(572, 74); this.gridResources.TabIndex = 3; @@ -148,7 +149,7 @@ private void InitializeComponent() this.lblFilter.Location = new System.Drawing.Point(0, 2); this.lblFilter.Name = "lblFilter"; this.lblFilter.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0); - this.lblFilter.Size = new System.Drawing.Size(39, 20); + this.lblFilter.Size = new System.Drawing.Size(39, 15); this.lblFilter.TabIndex = 0; this.lblFilter.Text = "lblFilter"; // From dd5b0cb8da161648536b851042f0735eff776121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 12 Jun 2021 17:46:32 +0200 Subject: [PATCH 111/211] Adding validation info to downloadable languages --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 8 ++++++- KGySoft.Drawing.ImagingTools/Res.cs | 8 ++++++- .../ViewModel/DownloadResourcesViewModel.cs | 4 ++-- .../ViewModel/DownloadableResourceItem.cs | 23 ++++++++++++++++++- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 7e32024..1bc81b2 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -438,12 +438,15 @@ otherwise, the result might not be optimal even with dithering. {0} file(s) have been downloaded. -The culture of one or more downloaded localizations is not supported on this platform. Those languages will not appear in the list. +The culture of one or more downloaded localizations is not supported on this platform. Those languages will not appear among the selectable languages. The selected quantizer supports partial transparency, which is not supported by ditherers, so partial transparent pixels will be blended with back color. + + This language is not supported on this platform or by the executing framework. + Are you sure you want to overwrite this installation? @@ -493,6 +496,9 @@ It is possible that is has no effect. Without selecting a quantizer possible alpha pixels of the source image are blended with black. By selecting a quantizer you can specify a different back color. + + This item is for a different ImagingTools version. + A quantizer has been auto selected for pixel format '{0}' using default settings. Use a specific instance to adjust parameters. diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index e90613c..dac1261 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -197,6 +197,9 @@ internal static class Res /// so partial transparent pixels will be blended with back color. internal static string WarningMessageDithererNoAlphaGradient => Get("WarningMessage_DithererNoAlphaGradient"); + /// This language is not supported on this platform or by the executing framework + internal static string WarningMessageUnsupportedCulture => Get("WarningMessage_UnsupportedCulture"); + /// Are you sure you want to overwrite this installation? internal static string ConfirmMessageOverwriteInstallation => Get("ConfirmMessage_OverwriteInstallation"); @@ -232,6 +235,9 @@ internal static class Res /// By selecting a quantizer you can specify a different back color. internal static string InfoMessageAlphaTurnsBlack => Get("InfoMessage_AlphaTurnsBlack"); + /// This item is for a different ImagingTools version. + internal static string InfoMessageResourceVersionMismatch => Get("InfoMessage_ResourceVersionMismatch"); + #endregion #region Installations @@ -545,7 +551,7 @@ internal static string InfoColor(int argb, string knownColors, string systemColo /// {0} file(s) have been downloaded. /// - /// The culture of one or more downloaded localizations is not supported on this platform. Those languages will not appear in the list. + /// The culture of one or more downloaded localizations is not supported on this platform. Those languages will not appear among the selectable languages. internal static string WarningMessageDownloadCompletedWithUnsupportedCultures(int count) => Get("WarningMessage_DownloadCompletedWithUnsupportedCulturesFormat", count); /// The extension of the provided filename '{0}' does not match to the selected format ({1}). diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index 011f155..5e9a43f 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -369,15 +369,15 @@ private void OnDownloadCommand() { var toDownload = new List(); var existingFiles = new List(); - Version toolVersion = GetType().Assembly.GetName().Version.Normalize(); bool ignoreVersionMismatch = false; + Version selfVersion = InstallationManager.ImagingToolsVersion; foreach (DownloadableResourceItem item in Items!) { if (!item.Selected) continue; - if (!ignoreVersionMismatch && item.Info.ImagingToolsVersion.Normalize() != toolVersion) + if (!ignoreVersionMismatch && !selfVersion.NormalizedEquals(item.Info.ImagingToolsVersion)) { if (Confirm(Res.ConfirmMessageResourceVersionMismatch, false)) ignoreVersionMismatch = true; diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs index b1102ae..23a08cb 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs @@ -25,11 +25,12 @@ namespace KGySoft.Drawing.ImagingTools.ViewModel { - internal class DownloadableResourceItem : ObservableObjectBase + internal class DownloadableResourceItem : ObservableObjectBase, IValidatingObject { #region Fields private string? language; + private ValidationResultsCollection? validationResults; #endregion @@ -50,6 +51,10 @@ internal class DownloadableResourceItem : ObservableObjectBase public string ImagingToolsVersion => Info.ImagingToolsVersion.ToString(); public string? Description => Info.Description; + // We could just derive from ValidatingObjectBase but we the validation does not depend on property change we provide a lightweight implementation + public bool IsValid => true; + public ValidationResultsCollection ValidationResults => validationResults ??= CreateValidationResults(); + #endregion #region Internal Properties @@ -67,5 +72,21 @@ internal class DownloadableResourceItem : ObservableObjectBase internal DownloadableResourceItem(LocalizationInfo info) => Info = info; #endregion + + #region Methods + + private ValidationResultsCollection CreateValidationResults() + { + var result = new ValidationResultsCollection(); + + if (!InstallationManager.ImagingToolsVersion.NormalizedEquals(Info.ImagingToolsVersion)) + result.AddInfo(nameof(ImagingToolsVersion), Res.InfoMessageResourceVersionMismatch); + if (!ResHelper.TryGetCulture(CultureName, out var _)) + result.AddWarning(nameof(Language), Res.WarningMessageUnsupportedCulture); + + return result; + } + + #endregion } } \ No newline at end of file From d099e7b18573f3c9a5fa9c23e457a312728355b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 12 Jun 2021 17:54:25 +0200 Subject: [PATCH 112/211] Fixing issues on different platforms --- .../View/Controls/AdvancedDataGridView.cs | 12 +++++++++++- .../_Classes/InstallationManager.cs | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs index 708470b..229060a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs @@ -31,6 +31,16 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls { + #region Usings + +#if NET35 + using ValidationList = IList; +#else + using ValidationList = IReadOnlyList; +#endif + + #endregion + /// /// Just a DataGridView that /// - provides some default style with a few fixed issues @@ -258,7 +268,7 @@ private void DrawValidationIcon(DataGridViewCellPaintingEventArgs e) { #region Local Methods - static bool HasMatch(IList list, string name) + static bool HasMatch(ValidationList list, string name) { // not using LINQ and delegates for better performance int len = list.Count; diff --git a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs index 49fb6f0..50d1663 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs @@ -18,6 +18,9 @@ using System; using System.IO; +#if !NET35 +using System.Linq; +#endif using System.Security; using KGySoft.CoreLibraries; From f18304fad2a9856ad5b315db0a1c6e586b19dfc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 12 Jun 2021 20:33:56 +0200 Subject: [PATCH 113/211] The current item can be null --- .../View/Forms/MvvmBaseForm.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index 61090a7..48378a4 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -267,14 +267,14 @@ private void OnDisplayLanguageChangedCommand() ApplyStringResources(); } - private void OnValidationResultsChangedCommand(ValidationResultsCollection validationResults) + private void OnValidationResultsChangedCommand(ValidationResultsCollection? validationResults) { foreach (KeyValuePair mapping in ValidationMapping) { - var propertyResults = validationResults[mapping.Key]; // var is IList in .NET 3.5 and IReadOnlyList above - ValidationResult? error = propertyResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Error); - ValidationResult? warning = error == null ? propertyResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Warning) : null; - ValidationResult? info = error == null && warning == null ? propertyResults.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Information) : null; + var propertyResults = validationResults?[mapping.Key]; // var is IList in .NET 3.5 and IReadOnlyList above + ValidationResult? error = propertyResults?.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Error); + ValidationResult? warning = error == null ? propertyResults?.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Warning) : null; + ValidationResult? info = error == null && warning == null ? propertyResults?.FirstOrDefault(vr => vr.Severity == ValidationSeverity.Information) : null; ErrorProvider.SetError(mapping.Value, error?.Message); WarningProvider.SetError(mapping.Value, warning?.Message); InfoProvider.SetError(mapping.Value, info?.Message); From 9d89e7af96c6d7fbc9f2d4de31c6b59441d137aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 12 Jun 2021 20:35:05 +0200 Subject: [PATCH 114/211] Allowing the localized button growing --- .../View/Forms/AdjustColorsFormBase.Designer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.Designer.cs index 137ddc4..57dc1fb 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.Designer.cs @@ -45,6 +45,7 @@ private void InitializeComponent() // // btnReset // + this.btnReset.AutoSize = true; this.btnReset.Dock = System.Windows.Forms.DockStyle.Right; this.btnReset.FlatStyle = System.Windows.Forms.FlatStyle.System; this.btnReset.Location = new System.Drawing.Point(196, 25); From bf935b6368d28dcd125542101fd5ff5c8468f42c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 14 Jun 2021 09:29:20 +0200 Subject: [PATCH 115/211] Improving filtering: combining culture-specific search with ordinal to provide better results --- .../ViewModel/EditResourcesViewModel.cs | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index 8ceb841..079dfe1 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -52,10 +52,20 @@ internal class EditResourcesViewModel : ViewModelBase { #region Constants + #region Internal Constants + internal const string StateSaveExecutedWithError = nameof(StateSaveExecutedWithError); #endregion + #region Private Constants + + private const CompareOptions cultureSpecificCompareOptions = CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth; + + #endregion + + #endregion + #region Fields private readonly CultureInfo culture; @@ -192,8 +202,7 @@ private void ApplySelection() private void ApplyFilter(IList set) { - var oldSet = FilteredSet as SortableBindingList; - if (oldSet != null) + if (FilteredSet is SortableBindingList oldSet) oldSet.ListChanged -= FilteredSet_ListChanged; if (set.Count == 0) @@ -210,13 +219,17 @@ private void ApplyFilter(IList set) else { newSet = new SortableBindingList(new List()); - CompareInfo compareInfo = culture.CompareInfo; - CompareOptions options = CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreWidth; + CompareInfo cultureSpecificInfo = culture.CompareInfo; + CompareInfo invariantInfo = CultureInfo.InvariantCulture.CompareInfo; foreach (ResourceEntry entry in set) { - if (compareInfo.IndexOf(entry.Key, filter, options) >= 0 - || compareInfo.IndexOf(entry.OriginalText, filter, options) >= 0 - || compareInfo.IndexOf(entry.TranslatedText, filter, options) >= 0) + // Using ordinal search for key, invariant for original text (to allow ignoring char width, for example), + // and both ordinal and culture-specific search for the translated text because culture specific fails to match some patterns, + // eg.: "Vissza" is not found with the search term "viss" using the Hungarian culture. + if (entry.Key.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0 + || invariantInfo.IndexOf(entry.OriginalText, filter, cultureSpecificCompareOptions) >= 0 + || entry.TranslatedText.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0 + || cultureSpecificInfo.IndexOf(entry.TranslatedText, filter, cultureSpecificCompareOptions) >= 0) { newSet.Add(entry); } From 8d6dbe09311f6f3ffa801ee80a4c0e509f16a48a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 14 Jun 2021 21:06:32 +0200 Subject: [PATCH 116/211] ImageViewer refactoring: extracting generator, making fast default image generating async. Disabling preview to be refactored later. --- .../ImageViewer.DisplayImageGenerator.cs | 744 ++++++++++++++++++ .../View/Controls/ImageViewer.cs | 547 ++----------- .../ViewModel/ImageVisualizerViewModel.cs | 4 +- 3 files changed, 791 insertions(+), 504 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs new file mode 100644 index 0000000..b491ba8 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs @@ -0,0 +1,744 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: ImageViewer.DisplayImageGenerator.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2020 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.Threading; + +using KGySoft.CoreLibraries; +using KGySoft.Drawing.Imaging; +using KGySoft.Drawing.ImagingTools.Model; +using KGySoft.Reflection; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View.Controls +{ + internal partial class ImageViewer + { + #region PreviewGenerator class + + private sealed class DisplayImageGenerator : IDisposable + { + #region Nested classes + + #region GenerateDefaultImageTask class + + private sealed class GenerateDefaultImageTask : AsyncTaskBase + { + #region Fields + + internal Bitmap SourceBitmap; + internal bool InvalidateOwner; + + #endregion + } + + #endregion + + // TODO + //private sealed class GenerateTask : AsyncTaskBase + //{ + // //#region Fields + + // //internal Image? SourceImage; + // //internal Size Size; + + // //#endregion + //} + + #endregion + + #region Constants + + private const int sizeThreshold = 1024; + + #endregion + + #region Fields + + #region Static Fields + + /// + /// These formats are not are not supported by Graphics even though a Bitmap can use them. + /// On Linux/Mono some formats are completely unsupported but they do not appear here. + /// + private static readonly PixelFormat[] unsupportedFormats = OSUtils.IsWindows + ? new[] { PixelFormat.Format16bppGrayScale } + : new[] { PixelFormat.Format16bppRgb555, PixelFormat.Format16bppRgb565 }; + + /// + /// These formats are so slow that if . + /// + private static readonly PixelFormat[] slowFormats = OSUtils.IsWindows + ? new[] { PixelFormat.Format48bppRgb, PixelFormat.Format64bppArgb, PixelFormat.Format64bppArgb } + : Reflector.EmptyArray(); + + #endregion + + #region Instance Fields + + private readonly ImageViewer owner; + private readonly object syncRootGenerate = new object(); + + private volatile bool disposed; + + /// + /// true if generator can generate new content. Turned off on low memory or by . Invalidating the image enables it again. + /// + private bool enabled; + + private AsyncTaskBase? generateDefaultImageTask; + private AsyncTaskBase? generateDisplayImageTask; + + /// + /// The default image to be displayed when no generated preview is needed or while generation is in progress. + /// Set by . If is true, then contains + /// - A fast PARGB32 clone of the original image it that is a Bitmap + /// - A clone of the original image if that is a Metafile so the original image will not be blocked to generate resized images + /// Otherwise, it is the same reference as the owner.Image. + /// If not null, then always contains a compatible, displayable image. + /// + private volatile Image? defaultDisplayImage; + private bool isDefaultImageCloned; + + // TODO + //private Image? sourceClone; // Used for creating the resized preview. Just to avoid "image is used elsewhere" + //private Size requestedSize; + + private volatile Image? adjustedDisplayImage; // The actual displayed image. If not null, it is either equals safeDefaultImage or cachedDisplayImage. + //private volatile Bitmap? cachedDisplayImage; // The lastly generated display image. Can be unused but is cached until a next preview is generated. + //private Size currentCachedDisplayImage; // just to cache cachedDisplayImage.Size, because accessing currentPreview can lead to "object is used elsewhere" error + + #endregion + + #endregion + + #region Constructors + + internal DisplayImageGenerator(ImageViewer owner) + { + this.owner = owner; + enabled = true; + } + + #endregion + + #region Methods + + #region Static Methods + + private static void CancelRunningGenerate(AsyncTaskBase? task) + { + if (task == null) + return; + task.IsCanceled = true; + } + + private static void WaitForPendingGenerate(ref AsyncTaskBase? task) + { + if (task == null) + return; + task.WaitForCompletion(); + task.Dispose(); + task = null; + } + + #endregion + + #region Instance Methods + + #region Public Methods + + public void Dispose() + { + if (disposed) + return; + Free(); + disposed = true; + } + + #endregion + + #region Internal Methods + + internal Image? GetDisplayImage() + { + #region Local Methods + + void SetOrGenerateDefaultDisplayImage() + { + Debug.Assert(owner.image != null && owner.pixelFormat != default); + if (!enabled) + return; + + Image image = owner.image!; + Bitmap? bitmap = image as Bitmap; + + // Metafile: The default image is the same as the original. If anti-aliased images are required, a clone is created on demand from that task + // Bitmap: generating a new default image for unsupported formats, + bool isGenerateNeeded = bitmap != null && (owner.pixelFormat.In(unsupportedFormats) + // non-PARGB32 images larger than 256x256 - note: leaving even slow formats unconverted below sizeThreshold / 4 + || owner.pixelFormat != PixelFormat.Format32bppPArgb && (owner.imageSize.Width > sizeThreshold >> 2 || owner.imageSize.Height > sizeThreshold >> 2) + // Raw icons: converting because icons are handled oddly by GDI+, for example, the first column has half pixel width + || bitmap.RawFormat.Guid == ImageFormat.Icon.Guid); + + if (!isGenerateNeeded) + { + // A generated default image is set from another thread so handling possible concurrency. + if (defaultDisplayImage == null) + Interlocked.CompareExchange(ref defaultDisplayImage, image, null); + return; + } + + // We check if there is a generate in progress. If not, then we start a new one (if it has just been finished). + AsyncTaskBase? task = generateDefaultImageTask; + if (task != null) + return; + + task = new GenerateDefaultImageTask + { + SourceBitmap = bitmap!, + InvalidateOwner = bitmap!.RawFormat.Guid == ImageFormat.Icon.Guid + }; + generateDefaultImageTask = task; + ThreadPool.QueueUserWorkItem(GenerateDefaultImage, task); + } + + #endregion + + Debug.Assert(owner.image != null); + + // 1.) There is a size adjusted display image= + Image? result = adjustedDisplayImage; + if (result != null) + return result; + + // 2.) Checking if there is an already available (fast) default image + result = defaultDisplayImage; + if (result != null) + return result; + + SetOrGenerateDefaultDisplayImage(); + + // Waiting for the task to be finished if pixel format is not supported, + // or it is so slow (>= 48bpp) that it is faster to wait for the converted image and paint that one. + // Note: Not using async because this project targets also .NET 3.5 and the image is locked anyway + if (owner.pixelFormat.In(unsupportedFormats) || owner.pixelFormat.In(slowFormats)) + generateDefaultImageTask?.WaitForCompletion(); + + // 3.) Returning possibly generated display image or falling back to the original image + if (!enabled) + { + Free(); + + // So next time we will return sooner, at 2.) + return defaultDisplayImage = owner.image; + } + + return adjustedDisplayImage ?? defaultDisplayImage ?? owner.image; + } + + /// + /// Invalidates everything and resets the default image if it is not needed to be generated. + /// + internal void InvalidateImages() + { + // This cancels all tasks and disposes every generating resources + Free(); + Debug.Assert(generateDefaultImageTask == null); + + // (Re-)enabling generating images + enabled = true; + + // TODO + //Image? image = owner.image; + //if (image == null) + // return; + + //Debug.Assert(owner.pixelFormat != default, "PixelFormat must be known if image is not null"); + //// Metafile: no generating needed, the default image is the same as the original + //// (for Metafiles a clone is created on demand is anti-aliased images have to be created to prevent locking on the displayed image) + //if (image is not Bitmap bitmap) + //{ + // defaultImage = image; + // return; + //} + + //// Bitmap: generating a new default image for unsupported formats, + //bool isGenerateNeeded = owner.pixelFormat.In(unsupportedFormats) + // // non-PARGB32 images larger than 256x256 - note: leaving even slow formats unconverted below sizeThreshold / 4 + // || owner.pixelFormat != PixelFormat.Format32bppPArgb && (owner.imageSize.Width > sizeThreshold >> 2 || owner.imageSize.Height > sizeThreshold >> 2) + // // Raw icons: converting because icons are handled oddly by GDI+, for example, the first column has half pixel width + // || bitmap.RawFormat.Guid == ImageFormat.Icon.Guid; + + //// If generate is needed, then leaving defaultImage null and starting a generate when an actual Paint is triggered. + //// Starting the task here would just be slower anyway because it locks the image, which wouldn't let the Paint draw the original image until it ends. + //if (!isGenerateNeeded) + // defaultImage = image; + } + + /// + /// Invalidates the display image and if required, starts to generate a new one + /// + internal void InvalidateDisplayImage() + { + // todo: mainly the old BeginGenerateDisplayImage + // TODO: handle possible defaultImage generation + } + + // TODO + //internal void BeginGenerateDisplayImage() + //{ + // CancelRunningGenerate(); + // if (!enabled) + // return; + + // // TODO + // //Image? image = owner.image; + // //if (image == null) + // //{ + // // Debug.Assert(cachedDisplayImage == null && displayImage == null); + // // return; + // //} + + // //Size size = owner.targetRectangle.Size; + // //bool isGenerateNeeded = owner.isMetafile + // // ? owner.smoothZooming + // // : owner.smoothZooming && owner.zoom < 1f && (owner.imageSize.Width > sizeThreshold || owner.imageSize.Height > sizeThreshold); + + // //if (!isGenerateNeeded || size.Width < 1 || size.Height < 1) + // //{ + // // displayImage = safeDefaultImage; + // // return; + // //} + + // //requestedSize = size; + // //ThreadPool.QueueUserWorkItem(DoGenerate!, new GenerateTask { SourceImage = sourceClone, Size = size }); + //} + + #endregion + + #region Private Methods + + private void Free() + { + // to prevent starting new tasks while freeing resources + enabled = false; + CancelRunningGenerate(generateDefaultImageTask); + CancelRunningGenerate(generateDisplayImageTask); + + lock (syncRootGenerate) + { + WaitForPendingGenerate(ref generateDefaultImageTask); + WaitForPendingGenerate(ref generateDisplayImageTask); + + // TODO + //requestedSize = default; + //displayImage = null; + //sourceClone?.Dispose(); + //sourceClone = null; + + // ImageViewer also locks on the private generator when obtains display image so this ensures that no disposed image is painted. + lock (this) + { + if (isDefaultImageCloned) + defaultDisplayImage?.Dispose(); + defaultDisplayImage = null; + isDefaultImageCloned = false; + } + + // TODO + //FreeCachedPreview(); + } + } + + private void GenerateDefaultImage(object state) + { + #region Local Methods + + static Bitmap? DoGenerateDefaultImage(GenerateDefaultImageTask task, ref bool enabled) + { + Size size = task.SourceBitmap.Size; + + // skipping generating clone if there is not enough memory and it would only serve performance + // x4: because we want to convert to 32bpp + long managedPressure = size.Width * size.Height * 4; + if (!MemoryHelper.CanAllocate(managedPressure) && !task.SourceBitmap.PixelFormat.In(unsupportedFormats)) + { + Debug.WriteLine($"Discarding task because there is no {managedPressure:N0} bytes of available managed memory"); + task.IsCanceled = true; + } + + Debug.WriteLine($"Generating fast default image on thread #{Thread.CurrentThread.ManagedThreadId}"); + Bitmap? result = null; + try + { + result = new Bitmap(size.Width, size.Height, PixelFormat.Format32bppPArgb); + using IReadableBitmapData src = task.SourceBitmap.GetReadableBitmapData(); + using IWritableBitmapData dst = result.GetWritableBitmapData(); + + // here allowing to use max parallelization as the original image is locked anyway + var cfg = new AsyncConfig { IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false }; + + // Not using Task and await because we want to be compatible with .NET 3.5, too. + IAsyncResult asyncResult = src.BeginCopyTo(dst, asyncConfig: cfg); + + // As we are already on a pool thread the End... call does not block the UI. + asyncResult.EndCopyTo(); + } + catch (Exception e) when (!e.IsCriticalGdi()) + { + // Despite all of the preconditions the memory could not be allocated or some other error occurred (yes, we catch even OutOfMemoryException here) + // NOTE: practically we always can recover from here: we simply don't use a generated clone and the worker thread can be finished + task.IsCanceled = true; + enabled = false; + } + finally + { + if (task.IsCanceled) + { + Debug.WriteLine("Canceled default image generate"); + result?.Dispose(); + result = null; + } + } + + return result; + } + + #endregion + + var task = (GenerateDefaultImageTask)state; + + // ensuring that only one generate task is running at once + lock (syncRootGenerate) + { + // lost race + if (!enabled || task.SourceBitmap != owner.image || disposed) + { + task.Dispose(); + return; + } + + // from now on the task can be canceled + generateDefaultImageTask = task; + + try + { + Bitmap? result = null; + + // unless already replaced, this is the same as the owner.image so locking on it to avoid the possible "bitmap region is already locked" issue. + lock (task.SourceBitmap) + { + try + { + // Generating the actual result. IsCanceled might be true if the lock above could not be immediately acquired + if (!task.IsCanceled) + result = DoGenerateDefaultImage(task, ref enabled); + } + finally + { + task.SetCompleted(); + } + } + + if (result == null || task.IsCanceled) + return; + + defaultDisplayImage = result; + isDefaultImageCloned = true; + + // only for icons because otherwise the appearance is the same + if (task.InvalidateOwner) + owner.Invalidate(); + } + finally + { + task.Dispose(); + generateDefaultImageTask = null; + } + } + } + + // TODO + //private bool TrySetPreview(Image? reference, Size size) + //{ + // if (sourceClone != null && reference != sourceClone) + // { + // Debug.Assert(cachedDisplayImage != displayImage, "If image has been replaced in owner, its display image is not expected to be cached here"); + // FreeCachedPreview(); + // return false; + // } + + // // we don't free generated preview here maybe it can be re-used later (eg. toggling metafile smooth zooming) + // if (currentCachedDisplayImage != size) + // return false; + + // if (displayImage == cachedDisplayImage) + // return true; + + // Debug.WriteLine($"Re-using pregenerated preview of size {size.Width}x{size.Height}"); + // displayImage = cachedDisplayImage; + // owner.Invalidate(); + // return true; + //} + + //private void FreeCachedPreview() + //{ + // lock (this) // It is alright, this is a private class. ImageViewer also locks on this instance when obtains display image so this ensures that no disposed image is painted. + // { + // if (displayImage != null && displayImage == cachedDisplayImage) + // { + // displayImage = null; + // owner.Invalidate(); + // } + + // Bitmap? toFree = cachedDisplayImage; + // cachedDisplayImage = null; + // toFree?.Dispose(); + // currentCachedDisplayImage = default; + // } + //} + + //private void DoGenerate(object state) + //{ + // var task = (GenerateTask)state; + + // // this is a fairly large lock ensuring that only one generate task is running at once + // lock (syncRootGenerate) + // { + // // lost race + // if (!enabled || task.SourceImage != sourceClone || task.Size != requestedSize) + // { + // task.Dispose(); + // return; + // } + + // // checking if we already have the preview + // if (!task.IsCanceled) + // { + // if (TrySetPreview(task.SourceImage, task.Size)) + // { + // task.Dispose(); + // return; + // } + + // // Before creating the preview releasing previous cached result. It is important to free it here, before checking the free memory. + // FreeCachedPreview(); + // } + + // if (task.SourceImage == null) + // { + // Debug.Assert(sourceClone == null && owner.image != null); + // Image image = owner.image!; + + // // As OnPaint can occur any time in the UI thread we lock on it. See also PaintImage. + // lock (image) + // { + // try + // { + // // A clone must be created to use the image without locking later on and getting an "object is used elsewhere" error from paint. + // // This is created synchronously so it can be used as a reference in the generating tasks. + // if (owner.isMetafile) + // sourceClone = (Metafile)image.Clone(); + // else + // { + // PixelFormat pixelFormat = image.PixelFormat; + // var bmp = (Bitmap)image; + + // // clone is tried to be compact, fast and compatible + // sourceClone = pixelFormat.In(PixelFormat.Format32bppArgb, PixelFormat.Format32bppPArgb, PixelFormat.Format64bppArgb, PixelFormat.Format64bppPArgb) ? bmp.ConvertPixelFormat(PixelFormat.Format32bppPArgb) + // : pixelFormat.In(PixelFormat.Format24bppRgb, PixelFormat.Format32bppRgb, PixelFormat.Format48bppRgb) ? bmp.ConvertPixelFormat(PixelFormat.Format24bppRgb) + // : pixelFormat == PixelFormat.Format16bppGrayScale ? bmp.ConvertPixelFormat(PixelFormat.Format8bppIndexed, PredefinedColorsQuantizer.Grayscale()) + // : pixelFormat.In(convertedFormats) ? bmp.ConvertPixelFormat(PixelFormat.Format32bppPArgb) + // : bmp.CloneCurrentFrame(); + // } + + // task.SourceImage = sourceClone; + // } + // catch (Exception e) when (!e.IsCriticalGdi()) + // { + // // Disabling preview generation if we could not create the clone (eg. on low memory) + // // It will be re-enabled when owner.Image is reset. + // enabled = false; + // sourceClone?.Dispose(); + // sourceClone = null; + // return; + // } + // } + // } + + // Debug.Assert(activeTask?.IsCanceled != false); + // WaitForPendingGenerate(); + // Debug.Assert(activeTask == null); + + // // from now on the task can be canceled + // activeTask = task; + + // try + // { + // Bitmap? result = null; + // try + // { + // if (!task.IsCanceled) + // result = task.SourceImage is Metafile ? GenerateMetafilePreview(task) : GenerateBitmapPreview(task); + // } + // finally + // { + // task.SetCompleted(); + // } + + // if (result != null) + // { + // // setting latest cache (even if the task has been canceled since the generating the completed result) + // currentCachedDisplayImage = task.Size; + // cachedDisplayImage = result; + // } + + // if (task.IsCanceled) + // return; + + // Debug.WriteLine("Applying generated result"); + // Debug.Assert(displayImage == null || displayImage == safeDefaultImage || displayImage == owner.image, "Display image is not the same as the original one: dispose is necessary"); + + // // not freeing the display image because it is always the original image here + // displayImage = result; + // owner.Invalidate(); + // } + // finally + // { + // task.Dispose(); + // activeTask = null; + // } + // } + //} + + //private static Bitmap? GenerateMetafilePreview(GenerateTask task) + //{ + // // For the resizing large managed buffer of source.Height * target.Width of ColorF (16 bytes) is allocated internally. To be safe we count with the doubled sizes. + // Size doubledSize = new Size(task.Size.Width << 1, task.Size.Height << 1); + // long managedPressure = doubledSize.Width * doubledSize.Height * 16; + // if (!MemoryHelper.CanAllocate(managedPressure)) + // { + // Debug.WriteLine($"Discarding task because there is no {managedPressure:N0} bytes of available managed memory"); + // task.IsCanceled = true; + // } + + // if (task.IsCanceled) + // return null; + // // MetafileExtensions.ToBitmap does the same if anti aliasing is requested but this way the process can be canceled + // Debug.WriteLine($"Generating anti aliased image {task.Size.Width}x{task.Size.Height} on thread #{Thread.CurrentThread.ManagedThreadId}"); + // Bitmap? result = null; + // Bitmap? doubled = null; + // try + // { + // doubled = new Bitmap(task.SourceImage!, task.Size.Width << 1, task.Size.Height << 1); + // if (!task.IsCanceled) + // { + // result = new Bitmap(task.Size.Width, task.Size.Height, PixelFormat.Format32bppPArgb); + // using IReadableBitmapData src = doubled.GetReadableBitmapData(); + // using IReadWriteBitmapData dst = result.GetReadWriteBitmapData(); + + // // not using Task and await, because this method's signature must match the WaitCallback delegate, and we want to be compatible with .NET 3.5, too + // IAsyncResult asyncResult = src.BeginDrawInto(dst, new Rectangle(Point.Empty, doubled.Size), new Rectangle(Point.Empty, result.Size), + // // ReSharper disable once AccessToModifiedClosure - intended, if IsCanceled is modified we need to return its modified value + // asyncConfig: new AsyncConfig { IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false }); + + // // As we are already on a pool thread this is not a UI blocking call + // // This will throw an exception if resizing failed (resizing also allocates a large amount of memory). + // asyncResult.EndDrawInto(); + // } + // } + // catch (Exception e) when (!e.IsCriticalGdi()) + // { + // // Despite all of the preconditions the memory could not be allocated or some other error occurred (yes, we catch even OutOfMemoryException here) + // // NOTE: practically we always can recover from here: we simply don't use a generated preview and the worker thread can be finished + // task.IsCanceled = true; + // } + // finally + // { + // doubled?.Dispose(); + // if (task.IsCanceled) + // { + // result?.Dispose(); + // result = null; + // } + // } + + // return result; + //} + + //private static Bitmap? GenerateBitmapPreview(GenerateTask task) + //{ + // // BitmapExtensions.Resize does the same but this way the process can be canceled + // Debug.WriteLine($"Generating smoothed image {task.Size.Width}x{task.Size.Height} on thread #{Thread.CurrentThread.ManagedThreadId}"); + + // Bitmap? result = null; + // try + // { + // result = new Bitmap(task.Size.Width, task.Size.Height, PixelFormat.Format32bppPArgb); + // using IReadableBitmapData src = ((Bitmap)task.SourceImage!).GetReadableBitmapData(); + // using IReadWriteBitmapData dst = result.GetReadWriteBitmapData(); + // var cfg = new AsyncConfig { IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false, MaxDegreeOfParallelism = Environment.ProcessorCount >> 1 }; + + // // Not using Task and await, because this method's signature must match the WaitCallback delegate, and we want to be compatible with .NET 3.5, too. + // // As we are already on a pool thread the End... call does not block the UI. + // var srcRect = new Rectangle(Point.Empty, task.SourceImage!.Size); + // var dstRect = new Rectangle(Point.Empty, task.Size); + // if (srcRect == dstRect) + // { + // IAsyncResult asyncResult = src.BeginCopyTo(dst, srcRect, Point.Empty, asyncConfig: cfg); + // asyncResult.EndCopyTo(); + // } + // else + // { + // IAsyncResult asyncResult = src.BeginDrawInto(dst, srcRect, dstRect, asyncConfig: cfg); + // asyncResult.EndDrawInto(); + // } + // } + // catch (Exception e) when (!e.IsCriticalGdi()) + // { + // // Despite all of the preconditions the memory could not be allocated or some other error occurred (yes, we catch even OutOfMemoryException here) + // // NOTE: practically we always can recover from here: we simply don't use a generated preview and the worker thread can be finished + // task.IsCanceled = true; + // } + // finally + // { + // if (task.IsCanceled) + // { + // result?.Dispose(); + // result = null; + // } + // } + + // return result; + //} + + #endregion + + #endregion + + #endregion + } + + #endregion + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index dad38fa..395c96a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -17,16 +17,12 @@ #region Usings using System; -using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Threading; using System.Windows.Forms; -using KGySoft.CoreLibraries; -using KGySoft.Drawing.Imaging; -using KGySoft.Drawing.ImagingTools.Model; using KGySoft.Drawing.ImagingTools.WinApi; #endregion @@ -38,8 +34,6 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls /// internal partial class ImageViewer : BaseControl { - #region Nested types - #region InvalidateFlags enum [Flags] @@ -48,510 +42,42 @@ private enum InvalidateFlags None, Sizes = 1, DisplayImage = 1 << 1, - All = Sizes | DisplayImage - } - - #endregion - - #region PreviewGenerator class - - private sealed class PreviewGenerator : IDisposable - { - #region Nested classes - - private sealed class GenerateTask : AsyncTaskBase - { - #region Fields - - internal Image? SourceImage; - internal Size Size; - - #endregion - } - - #endregion - - #region Fields - - private readonly ImageViewer owner; - private readonly object syncRootGenerate = new object(); - - private bool enabled; - private GenerateTask? activeTask; - - private Image? sourceClone; - private Image? safeDefaultImage; // The default image displayed when no generated preview is needed or while generation is in progress - private bool isClonedSafeDefaultImage; - private Size requestedSize; - - private volatile Image? displayImage; // The actual displayed image. If not null, it is either equals safeDefaultImage or currentPreview. - private volatile Bitmap? cachedDisplayImage; // The lastly generated display image. Can be unused but is cached until a next preview is generated. - private Size currentCachedDisplayImage; // just to cache cachedDisplayImage.Size, because accessing currentPreview can lead to "object is used elsewhere" error - - #endregion - - #region Constructors - - internal PreviewGenerator(ImageViewer owner) - { - this.owner = owner; - enabled = true; - } - - #endregion - - #region Methods -#pragma warning disable CS1690 // Accessing a member on a field of a marshal-by-reference class may cause a runtime exception - false alarm, owner is never a remote object - - #region Public Methods - - public void Dispose() => Free(); - - #endregion - - #region Internal Methods - - internal Image? GetDisplayImage(bool generateSyncIfNull) - { - Image? result = displayImage; - if (result != null || !generateSyncIfNull) - return result; - - if (safeDefaultImage == null) - { - Debug.Assert(owner.image != null, "Image is not expected to be null here"); - Image image = owner.image!; - - // Locking on display image so if it is the same as the original image, which is also locked when accessing its bitmap data - // the "bitmap region is already locked" can be avoided. Important: this cannot be ensured without locking here internally because - // OnPaint can occur any time after invalidating. - lock (image) - { - PixelFormat pixelFormat = image.PixelFormat; - - try - { - // Converting non supported or too slow pixel formats - if (pixelFormat.In(convertedFormats) || pixelFormat != PixelFormat.Format32bppPArgb && (image.Width > sizeThreshold || image.Height > sizeThreshold)) - { - safeDefaultImage = image.ConvertPixelFormat(PixelFormat.Format32bppPArgb); - isClonedSafeDefaultImage = true; - } - - // Raw icons: converting because icons are handled oddly by GDI+, for example, the first column has half pixel width - else if (image is Bitmap bmp && bmp.RawFormat.Guid == ImageFormat.Icon.Guid) - { - isClonedSafeDefaultImage = true; - safeDefaultImage = bmp.CloneCurrentFrame(); - } - else - safeDefaultImage = image; - } - catch (Exception e) when (!e.IsCriticalGdi()) - { - // for converted formats trying again with more compact formats - if (pixelFormat.In(convertedFormats)) - { - try - { - safeDefaultImage = pixelFormat == PixelFormat.Format16bppGrayScale - ? image.ConvertPixelFormat(PixelFormat.Format8bppIndexed, PredefinedColorsQuantizer.Grayscale()) - : image.ConvertPixelFormat(pixelFormat.HasAlpha() ? PixelFormat.Format32bppPArgb : PixelFormat.Format24bppRgb); - isClonedSafeDefaultImage = true; - } - catch (Exception eInner) when (!eInner.IsCriticalGdi()) - { - // If pixel format is not supported at all then we let rendering die; otherwise, it may work but slowly or with visual glitches - isClonedSafeDefaultImage = false; - safeDefaultImage = image; - } - } - else - { - // It may happen if no clone could be created (maybe on low memory) - // Here it is not so important after all because we wanted to use it for performance reasons. - isClonedSafeDefaultImage = false; - safeDefaultImage = image; - } - } - } - } - - // it is possible that we have a displayImage now but if not we return the default - if (displayImage == null) - Interlocked.CompareExchange(ref displayImage, safeDefaultImage, null); - return displayImage; - } - - internal void BeginGenerateDisplayImage() - { - CancelRunningGenerate(); - if (!enabled) - return; - - Image? image = owner.image; - if (image == null) - { - Debug.Assert(cachedDisplayImage == null && displayImage == null); - return; - } - - Size size = owner.targetRectangle.Size; - bool isGenerateNeeded = owner.isMetafile - ? owner.smoothZooming - : owner.smoothZooming && owner.zoom < 1f && (owner.imageSize.Width > sizeThreshold || owner.imageSize.Height > sizeThreshold); - - if (!isGenerateNeeded || size.Width < 1 || size.Height < 1) - { - displayImage = safeDefaultImage; - return; - } - - requestedSize = size; - ThreadPool.QueueUserWorkItem(DoGenerate!, new GenerateTask { SourceImage = sourceClone, Size = size }); - } - - internal void Free() - { - CancelRunningGenerate(); - WaitForPendingGenerate(); - requestedSize = default; - displayImage = null; - sourceClone?.Dispose(); - sourceClone = null; - if (isClonedSafeDefaultImage) - safeDefaultImage?.Dispose(); - safeDefaultImage = null; - isClonedSafeDefaultImage = false; - FreeCachedPreview(); - enabled = true; - } - - #endregion - - #region Private Methods - - private void CancelRunningGenerate() - { - GenerateTask? runningTask = activeTask; - if (runningTask == null) - return; - runningTask.IsCanceled = true; - } - - private void WaitForPendingGenerate() - { - // In a non-UI thread it should be in a lock - GenerateTask? runningTask = activeTask; - if (runningTask == null) - return; - runningTask.WaitForCompletion(); - runningTask.Dispose(); - activeTask = null; - } - - private bool TrySetPreview(Image? reference, Size size) - { - if (sourceClone != null && reference != sourceClone) - { - Debug.Assert(cachedDisplayImage != displayImage, "If image has been replaced in owner, its display image is not expected to be cached here"); - FreeCachedPreview(); - return false; - } - - // we don't free generated preview here maybe it can be re-used later (eg. toggling metafile smooth zooming) - if (currentCachedDisplayImage != size) - return false; - - if (displayImage == cachedDisplayImage) - return true; - - Debug.WriteLine($"Re-using pregenerated preview of size {size.Width}x{size.Height}"); - displayImage = cachedDisplayImage; - owner.Invalidate(); - return true; - } - - private void FreeCachedPreview() - { - lock (this) // It is alright, this is a private class. ImageViewer also locks on this instance when obtains display image so this ensures that no disposed image is painted. - { - if (displayImage != null && displayImage == cachedDisplayImage) - { - displayImage = null; - owner.Invalidate(); - } - - Bitmap? toFree = cachedDisplayImage; - cachedDisplayImage = null; - toFree?.Dispose(); - currentCachedDisplayImage = default; - } - } - - private void DoGenerate(object state) - { - var task = (GenerateTask)state; - - // this is a fairly large lock ensuring that only one generate task is running at once - lock (syncRootGenerate) - { - // lost race - if (task.SourceImage != sourceClone || task.Size != requestedSize || !enabled) - { - task.Dispose(); - return; - } - - // checking if we already have the preview - if (!task.IsCanceled) - { - if (TrySetPreview(task.SourceImage, task.Size)) - { - task.Dispose(); - return; - } - - // Before creating the preview releasing previous cached result. It is important to free it here, before checking the free memory. - FreeCachedPreview(); - } - - if (task.SourceImage == null) - { - Debug.Assert(sourceClone == null && owner.image != null); - Image image = owner.image!; - - // As OnPaint can occur any time in the UI thread we lock on it. See also PaintImage. - lock (image) - { - try - { - // A clone must be created to use the image without locking later on and getting an "object is used elsewhere" error from paint. - // This is created synchronously so it can be used as a reference in the generating tasks. - if (owner.isMetafile) - sourceClone = (Metafile)image.Clone(); - else - { - PixelFormat pixelFormat = image.PixelFormat; - var bmp = (Bitmap)image; - - // clone is tried to be compact, fast and compatible - sourceClone = pixelFormat.In(PixelFormat.Format32bppArgb, PixelFormat.Format32bppPArgb, PixelFormat.Format64bppArgb, PixelFormat.Format64bppPArgb) ? bmp.ConvertPixelFormat(PixelFormat.Format32bppPArgb) - : pixelFormat.In(PixelFormat.Format24bppRgb, PixelFormat.Format32bppRgb, PixelFormat.Format48bppRgb) ? bmp.ConvertPixelFormat(PixelFormat.Format24bppRgb) - : pixelFormat == PixelFormat.Format16bppGrayScale ? bmp.ConvertPixelFormat(PixelFormat.Format8bppIndexed, PredefinedColorsQuantizer.Grayscale()) - : pixelFormat.In(convertedFormats) ? bmp.ConvertPixelFormat(PixelFormat.Format32bppPArgb) - : bmp.CloneCurrentFrame(); - } - - task.SourceImage = sourceClone; - } - catch (Exception e) when (!e.IsCriticalGdi()) - { - // Disabling preview generation if we could not create the clone (eg. on low memory) - // It will be re-enabled when owner.Image is reset. - enabled = false; - sourceClone?.Dispose(); - sourceClone = null; - return; - } - } - } - - Debug.Assert(activeTask?.IsCanceled != false); - WaitForPendingGenerate(); - Debug.Assert(activeTask == null); - - // from now on the task can be canceled - activeTask = task; - - try - { - Bitmap? result = null; - try - { - if (!task.IsCanceled) - result = task.SourceImage is Metafile ? GenerateMetafilePreview(task) : GenerateBitmapPreview(task); - } - finally - { - task.SetCompleted(); - } - - if (result != null) - { - // setting latest cache (even if the task has been canceled since the generating the completed result) - currentCachedDisplayImage = task.Size; - cachedDisplayImage = result; - } - - if (task.IsCanceled) - return; - - Debug.WriteLine("Applying generated result"); - Debug.Assert(displayImage == null || displayImage == safeDefaultImage || displayImage == owner.image, "Display image is not the same as the original one: dispose is necessary"); - - // not freeing the display image because it is always the original image here - displayImage = result; - owner.Invalidate(); - } - finally - { - task.Dispose(); - activeTask = null; - } - } - } - - private static Bitmap? GenerateMetafilePreview(GenerateTask task) - { - // For the resizing large managed buffer of source.Height * target.Width of ColorF (16 bytes) is allocated internally. To be safe we count with the doubled sizes. - Size doubledSize = new Size(task.Size.Width << 1, task.Size.Height << 1); - long managedPressure = doubledSize.Width * doubledSize.Height * 16; - if (!MemoryHelper.CanAllocate(managedPressure)) - { - Debug.WriteLine($"Discarding task because there is no {managedPressure:N0} bytes of available managed memory"); - task.IsCanceled = true; - } - - if (task.IsCanceled) - return null; - // MetafileExtensions.ToBitmap does the same if anti aliasing is requested but this way the process can be canceled - Debug.WriteLine($"Generating anti aliased image {task.Size.Width}x{task.Size.Height} on thread #{Thread.CurrentThread.ManagedThreadId}"); - Bitmap? result = null; - Bitmap? doubled = null; - try - { - doubled = new Bitmap(task.SourceImage!, task.Size.Width << 1, task.Size.Height << 1); - if (!task.IsCanceled) - { - result = new Bitmap(task.Size.Width, task.Size.Height, PixelFormat.Format32bppPArgb); - using IReadableBitmapData src = doubled.GetReadableBitmapData(); - using IReadWriteBitmapData dst = result.GetReadWriteBitmapData(); - - // not using Task and await, because this method's signature must match the WaitCallback delegate, and we want to be compatible with .NET 3.5, too - IAsyncResult asyncResult = src.BeginDrawInto(dst, new Rectangle(Point.Empty, doubled.Size), new Rectangle(Point.Empty, result.Size), - // ReSharper disable once AccessToModifiedClosure - intended, if IsCanceled is modified we need to return its modified value - asyncConfig: new AsyncConfig { IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false }); - - // As we are already on a pool thread this is not a UI blocking call - // This will throw an exception if resizing failed (resizing also allocates a large amount of memory). - asyncResult.EndDrawInto(); - } - } - catch (Exception e) when (!e.IsCriticalGdi()) - { - // Despite all of the preconditions the memory could not be allocated or some other error occurred (yes, we catch even OutOfMemoryException here) - // NOTE: practically we always can recover from here: we simply don't use a generated preview and the worker thread can be finished - task.IsCanceled = true; - } - finally - { - doubled?.Dispose(); - if (task.IsCanceled) - { - result?.Dispose(); - result = null; - } - } - - return result; - } - - private static Bitmap? GenerateBitmapPreview(GenerateTask task) - { - // BitmapExtensions.Resize does the same but this way the process can be canceled - Debug.WriteLine($"Generating smoothed image {task.Size.Width}x{task.Size.Height} on thread #{Thread.CurrentThread.ManagedThreadId}"); - - Bitmap? result = null; - try - { - result = new Bitmap(task.Size.Width, task.Size.Height, PixelFormat.Format32bppPArgb); - using IReadableBitmapData src = ((Bitmap)task.SourceImage!).GetReadableBitmapData(); - using IReadWriteBitmapData dst = result.GetReadWriteBitmapData(); - var cfg = new AsyncConfig { IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false, MaxDegreeOfParallelism = Environment.ProcessorCount >> 1 }; - - // Not using Task and await, because this method's signature must match the WaitCallback delegate, and we want to be compatible with .NET 3.5, too. - // As we are already on a pool thread the End... call does not block the UI. - var srcRect = new Rectangle(Point.Empty, task.SourceImage!.Size); - var dstRect = new Rectangle(Point.Empty, task.Size); - if (srcRect == dstRect) - { - IAsyncResult asyncResult = src.BeginCopyTo(dst, srcRect, Point.Empty, asyncConfig: cfg); - asyncResult.EndCopyTo(); - } - else - { - IAsyncResult asyncResult = src.BeginDrawInto(dst, srcRect, dstRect, asyncConfig: cfg); - asyncResult.EndDrawInto(); - } - } - catch (Exception e) when (!e.IsCriticalGdi()) - { - // Despite all of the preconditions the memory could not be allocated or some other error occurred (yes, we catch even OutOfMemoryException here) - // NOTE: practically we always can recover from here: we simply don't use a generated preview and the worker thread can be finished - task.IsCanceled = true; - } - finally - { - if (task.IsCanceled) - { - result?.Dispose(); - result = null; - } - } - - return result; - } - - #endregion - -#pragma warning restore CS1690 // Accessing a member on a field of a marshal-by-reference class may cause a runtime exception - #endregion + Image = 1 << 2, + All = Sizes | DisplayImage | Image } #endregion - #endregion - - #region Constants - - private const int sizeThreshold = 1024; - - #endregion - #region Fields #region Static Fields - private static readonly PixelFormat[] convertedFormats = OSUtils.IsWindows - // Windows: these are either not supported by Graphics or are very slow - ? new[] { PixelFormat.Format16bppGrayScale, PixelFormat.Format48bppRgb, PixelFormat.Format64bppArgb, PixelFormat.Format64bppPArgb } - // Non Windows (eg. Mono/Linux): these are not supported by Graphics - : new[] { PixelFormat.Format16bppRgb555, PixelFormat.Format16bppRgb565 }; - private static readonly Size referenceScrollSize = new Size(32, 32); #endregion #region Instance Fields - private readonly PreviewGenerator previewGenerator; + private readonly DisplayImageGenerator displayImageGenerator; private Image? image; private Rectangle targetRectangle; private Rectangle clientRectangle; - private bool smoothZooming; - private bool autoZoom; private float zoom = 1; private Size scrollbarSize; private Size imageSize; + private PixelFormat pixelFormat; + private bool isMetafile; + private bool smoothZooming; + private bool autoZoom; private bool sbHorizontalVisible; private bool sbVerticalVisible; - private int scrollFractionVertical; - private int scrollFractionHorizontal; private bool isApplyingZoom; private bool isDragging; + + private int scrollFractionVertical; + private int scrollFractionHorizontal; private Size draggingOrigin; private Point scrollingOrigin; @@ -653,7 +179,7 @@ public ImageViewer() sbVertical.ValueChanged += ScrollbarValueChanged; sbHorizontal.ValueChanged += ScrollbarValueChanged; - previewGenerator = new PreviewGenerator(this); + displayImageGenerator = new DisplayImageGenerator(this); } #endregion @@ -670,11 +196,21 @@ internal void UpdateImage() if (image == null) return; - // can happen when image is rotated - if (image.Size != imageSize || !ReferenceEquals(image, previewGenerator.GetDisplayImage(false))) - SetImage(image); - else - Invalidate(); + var flags = InvalidateFlags.Image | InvalidateFlags.DisplayImage; + Size newImageSize; + lock (image) + { + newImageSize = image.Size; + pixelFormat = image.PixelFormat; + } + + if (newImageSize != imageSize) + { + imageSize = newImageSize; + flags |= InvalidateFlags.Sizes; + } + + Invalidate(flags); } internal void IncreaseZoom() @@ -813,7 +349,7 @@ protected override void Dispose(bool disposing) sbHorizontal.ValueChanged -= ScrollbarValueChanged; if (disposing) - previewGenerator.Dispose(); + displayImageGenerator.Dispose(); base.Dispose(disposing); if (disposing) @@ -826,10 +362,10 @@ protected override void Dispose(bool disposing) private void SetImage(Image? value) { - previewGenerator.Free(); image = value; isMetafile = image is Metafile; imageSize = image?.Size ?? default; + pixelFormat = image?.PixelFormat ?? default; Invalidate(InvalidateFlags.All); // making sure image is not under or over-zoomed @@ -862,8 +398,12 @@ private void Invalidate(InvalidateFlags flags) if ((flags & InvalidateFlags.Sizes) != InvalidateFlags.None) AdjustSizes(); + if ((flags & InvalidateFlags.Image) != InvalidateFlags.None) + displayImageGenerator.InvalidateImages(); + + // this relies on the new calculated sizes so must come after adjusting sizes if ((flags & InvalidateFlags.DisplayImage) != InvalidateFlags.None) - previewGenerator.BeginGenerateDisplayImage(); + displayImageGenerator.InvalidateDisplayImage(); Invalidate(); } @@ -992,24 +532,25 @@ private void PaintImage(Graphics g) if (sbVerticalVisible) dest.Y -= sbVertical.Value; - // metafile or smoothing is off (smoothed metafile is generated async so it replaces the aliased result after some delay): NN - g.InterpolationMode = isMetafile || !smoothZooming ? InterpolationMode.NearestNeighbor - // large zoom or small shrunk image: BC because these cases it's not so slow - : zoom >= 4f || zoom < 1f && imageSize.Width <= sizeThreshold && imageSize.Height <= sizeThreshold ? InterpolationMode.HighQualityBicubic - // small zoom: BL for large images to prevent heavy lagging; otherwise, BC - : zoom > 1f ? imageSize.Width > sizeThreshold || imageSize.Height > sizeThreshold ? InterpolationMode.HighQualityBilinear : InterpolationMode.HighQualityBicubic - // anything else, including large shrunk images: NN (the good quality preview is generated async so it replaces the NN result after some delay) - : InterpolationMode.NearestNeighbor; + //// metafile or smoothing is off (smoothed metafile is generated async so it replaces the aliased result after some delay): NN + //g.InterpolationMode = isMetafile || !smoothZooming ? InterpolationMode.NearestNeighbor + // // large zoom or small shrunk image: BC because these cases it's not so slow + // : zoom >= 4f || zoom < 1f && imageSize.Width <= sizeThreshold && imageSize.Height <= sizeThreshold ? InterpolationMode.HighQualityBicubic + // // small zoom: BL for large images to prevent heavy lagging; otherwise, BC + // : zoom > 1f ? imageSize.Width > sizeThreshold || imageSize.Height > sizeThreshold ? InterpolationMode.HighQualityBilinear : InterpolationMode.HighQualityBicubic + // // anything else, including large shrunk images: NN (the good quality preview is generated async so it replaces the NN result after some delay) + // : InterpolationMode.NearestNeighbor; + g.InterpolationMode = isMetafile || !smoothZooming ? InterpolationMode.NearestNeighbor : InterpolationMode.HighQualityBicubic; g.PixelOffsetMode = PixelOffsetMode.HighQuality; // This lock ensures that no disposed image is painted. The generator also locks on itself when frees the cached preview. - lock (previewGenerator) + lock (displayImageGenerator) { // Locking on display image so if it is the same as the original image, which is also locked when accessing its bitmap data // the "bitmap region is already locked" can be avoided. Important: this cannot be ensured without locking here internally because // OnPaint can occur any time after invalidating. - Image toDraw = previewGenerator.GetDisplayImage(true)!; + Image toDraw = displayImageGenerator.GetDisplayImage(); bool useLock = image == toDraw; if (useLock) Monitor.Enter(toDraw); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs index 3b4a6a3..c6c17d6 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs @@ -1180,7 +1180,9 @@ private void OnShowPaletteCommand() for (int i = 0; i < newPalette.Length; i++) palette.Entries[i] = newPalette[i]; - currentImage.Image.Palette = palette; // the preview changes only if we apply the palette + // must be in a lock because it can be in use in the UI (where it is also locked) + lock (currentImage.Image) + currentImage.Image.Palette = palette; // the preview changes only if we apply the palette currentImage.Palette = palette.Entries; // the actual palette will be taken from here InvalidateImage(); } From 360c23ef3dc506a033793a90d5ba855815160d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 15 Jun 2021 13:12:45 +0200 Subject: [PATCH 117/211] Moving interpolation mode evaluation into the generator --- .../ImageViewer.DisplayImageGenerator.cs | 45 +++++++++++++------ .../View/Controls/ImageViewer.cs | 17 ++----- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs index b491ba8..820f412 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs @@ -19,12 +19,14 @@ using System; using System.Diagnostics; using System.Drawing; +using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Threading; using KGySoft.CoreLibraries; using KGySoft.Drawing.Imaging; using KGySoft.Drawing.ImagingTools.Model; +using KGySoft.Drawing.ImagingTools.View.Components; using KGySoft.Reflection; #endregion @@ -45,7 +47,7 @@ private sealed class GenerateDefaultImageTask : AsyncTaskBase { #region Fields - internal Bitmap SourceBitmap; + internal Bitmap SourceBitmap = default!; internal bool InvalidateOwner; #endregion @@ -85,7 +87,7 @@ private sealed class GenerateDefaultImageTask : AsyncTaskBase : new[] { PixelFormat.Format16bppRgb555, PixelFormat.Format16bppRgb565 }; /// - /// These formats are so slow that if . + /// These formats are so slow that it is still faster to generate a 32bpp clone first than display them directly. /// private static readonly PixelFormat[] slowFormats = OSUtils.IsWindows ? new[] { PixelFormat.Format48bppRgb, PixelFormat.Format64bppArgb, PixelFormat.Format64bppArgb } @@ -133,11 +135,7 @@ private sealed class GenerateDefaultImageTask : AsyncTaskBase #region Constructors - internal DisplayImageGenerator(ImageViewer owner) - { - this.owner = owner; - enabled = true; - } + internal DisplayImageGenerator(ImageViewer owner) => this.owner = owner; #endregion @@ -179,7 +177,7 @@ public void Dispose() #region Internal Methods - internal Image? GetDisplayImage() + internal (Image, InterpolationMode) GetDisplayImage() { #region Local Methods @@ -225,16 +223,35 @@ void SetOrGenerateDefaultDisplayImage() #endregion Debug.Assert(owner.image != null); + InterpolationMode interpolationMode = InterpolationMode.NearestNeighbor; - // 1.) There is a size adjusted display image= + // 1.) There is a size adjusted display image Image? result = adjustedDisplayImage; if (result != null) - return result; + return (result, interpolationMode); - // 2.) Checking if there is an already available (fast) default image + // 2.) Checking if there is an already available default image. It might have to be resized by painting. result = defaultDisplayImage; + + // Smoothing Bitmap: leaving NearestNeighbor if an adjusted image will be generated; otherwise, using some interpolation to be applied during painting + if (!owner.isMetafile && owner.smoothZooming) + { + float zoom = owner.zoom; + Size size = owner.imageSize; + + // Large zoom or shrunk image smaller than generating threshold: using HighQualityBicubic + if (zoom >= 4f || zoom < 1f && size.Width <= sizeThreshold && size.Height <= sizeThreshold) + interpolationMode = InterpolationMode.HighQualityBicubic; + // Small zoom: HighQualityBilinear for large images to prevent heavy lagging; otherwise, HighQualityBicubic + else if (zoom > 1f) + interpolationMode = size.Width > sizeThreshold || size.Height > sizeThreshold ? InterpolationMode.HighQualityBilinear : InterpolationMode.HighQualityBicubic; + // Shrinking larger images but generating is disabled: applying a fallback interpolation + else if (!enabled && zoom < 1f) + interpolationMode = owner.targetRectangle.Width > sizeThreshold || owner.targetRectangle.Height > sizeThreshold ? InterpolationMode.Bilinear : InterpolationMode.Bicubic; + } + if (result != null) - return result; + return (result, interpolationMode); SetOrGenerateDefaultDisplayImage(); @@ -250,10 +267,10 @@ void SetOrGenerateDefaultDisplayImage() Free(); // So next time we will return sooner, at 2.) - return defaultDisplayImage = owner.image; + return (defaultDisplayImage = owner.image!, interpolationMode); } - return adjustedDisplayImage ?? defaultDisplayImage ?? owner.image; + return (defaultDisplayImage ?? owner.image!, interpolationMode); } /// diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 395c96a..53ee115 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -532,25 +532,16 @@ private void PaintImage(Graphics g) if (sbVerticalVisible) dest.Y -= sbVertical.Value; - //// metafile or smoothing is off (smoothed metafile is generated async so it replaces the aliased result after some delay): NN - //g.InterpolationMode = isMetafile || !smoothZooming ? InterpolationMode.NearestNeighbor - // // large zoom or small shrunk image: BC because these cases it's not so slow - // : zoom >= 4f || zoom < 1f && imageSize.Width <= sizeThreshold && imageSize.Height <= sizeThreshold ? InterpolationMode.HighQualityBicubic - // // small zoom: BL for large images to prevent heavy lagging; otherwise, BC - // : zoom > 1f ? imageSize.Width > sizeThreshold || imageSize.Height > sizeThreshold ? InterpolationMode.HighQualityBilinear : InterpolationMode.HighQualityBicubic - // // anything else, including large shrunk images: NN (the good quality preview is generated async so it replaces the NN result after some delay) - // : InterpolationMode.NearestNeighbor; - - g.InterpolationMode = isMetafile || !smoothZooming ? InterpolationMode.NearestNeighbor : InterpolationMode.HighQualityBicubic; - g.PixelOffsetMode = PixelOffsetMode.HighQuality; - // This lock ensures that no disposed image is painted. The generator also locks on itself when frees the cached preview. lock (displayImageGenerator) { + (Image toDraw, InterpolationMode interpolationMode) = displayImageGenerator.GetDisplayImage(); + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.InterpolationMode = interpolationMode; + // Locking on display image so if it is the same as the original image, which is also locked when accessing its bitmap data // the "bitmap region is already locked" can be avoided. Important: this cannot be ensured without locking here internally because // OnPaint can occur any time after invalidating. - Image toDraw = displayImageGenerator.GetDisplayImage(); bool useLock = image == toDraw; if (useLock) Monitor.Enter(toDraw); From ab67fc872e0b88ca2749886ac779dbfb8699085f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 16 Jun 2021 13:53:59 +0200 Subject: [PATCH 118/211] ImageViewer: Finishing refactoring of DisplayImageGenerator --- .../ImageViewer.DisplayImageGenerator.cs | 873 +++++++++--------- .../View/Controls/ImageViewer.cs | 17 +- changelog.txt | 4 +- 3 files changed, 424 insertions(+), 470 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs index 820f412..52a11f8 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs @@ -26,11 +26,16 @@ using KGySoft.CoreLibraries; using KGySoft.Drawing.Imaging; using KGySoft.Drawing.ImagingTools.Model; -using KGySoft.Drawing.ImagingTools.View.Components; using KGySoft.Reflection; #endregion +#region Suppressions + +#pragma warning disable CS1690 // Accessing a member on a field of a marshal-by-reference class may cause a runtime exception - false alarm, ImageViewer is never a remote object. + +#endregion + namespace KGySoft.Drawing.ImagingTools.View.Controls { internal partial class ImageViewer @@ -55,16 +60,19 @@ private sealed class GenerateDefaultImageTask : AsyncTaskBase #endregion - // TODO - //private sealed class GenerateTask : AsyncTaskBase - //{ - // //#region Fields + #region GenerateResizedImageTask class + + private sealed class GenerateResizedImageTask : AsyncTaskBase + { + #region Fields + + internal Image SourceImage = default!; + internal Size Size; - // //internal Image? SourceImage; - // //internal Size Size; + #endregion + } - // //#endregion - //} + #endregion #endregion @@ -98,7 +106,6 @@ private sealed class GenerateDefaultImageTask : AsyncTaskBase #region Instance Fields private readonly ImageViewer owner; - private readonly object syncRootGenerate = new object(); private volatile bool disposed; @@ -107,32 +114,50 @@ private sealed class GenerateDefaultImageTask : AsyncTaskBase /// private bool enabled; - private AsyncTaskBase? generateDefaultImageTask; - private AsyncTaskBase? generateDisplayImageTask; + private GenerateDefaultImageTask? generateDefaultImageTask; + private GenerateResizedImageTask? generateResizedImageTask; /// - /// The default image to be displayed when no generated preview is needed or while generation is in progress. + /// The default image to be displayed when no resized display image is needed or while its generation is in progress. /// Set by . If is true, then contains /// - A fast PARGB32 clone of the original image it that is a Bitmap /// - A clone of the original image if that is a Metafile so the original image will not be blocked to generate resized images /// Otherwise, it is the same reference as the owner.Image. - /// If not null, then always contains a compatible, displayable image. + /// If is false, then may contain the original image even if it cannot be displayed. /// private volatile Image? defaultDisplayImage; - private bool isDefaultImageCloned; + private volatile bool isDefaultImageCloned; - // TODO - //private Image? sourceClone; // Used for creating the resized preview. Just to avoid "image is used elsewhere" - //private Size requestedSize; + /// + /// If not null, contains the last cached size-adjusted display image. + /// It is not disposed immediately when a new size () is started to be generated + /// so it can be re-used when toggling smooth zooming. + /// + private volatile Bitmap? resizedDisplayImage; - private volatile Image? adjustedDisplayImage; // The actual displayed image. If not null, it is either equals safeDefaultImage or cachedDisplayImage. - //private volatile Bitmap? cachedDisplayImage; // The lastly generated display image. Can be unused but is cached until a next preview is generated. - //private Size currentCachedDisplayImage; // just to cache cachedDisplayImage.Size, because accessing currentPreview can lead to "object is used elsewhere" error + /// + /// Just to cache .Size, + /// because accessing it on without locking can lead to "object is used elsewhere" error. + /// + private Size resizedDisplayImageSize; + + /// + /// The currently requested size of the size adjusted image. If it is the same as , + /// then can be displayed. + /// + private Size requestedSize; #endregion #endregion + #region Properties + + // This is alright, this class is private. + internal object SyncRoot => this; + + #endregion + #region Constructors internal DisplayImageGenerator(ImageViewer owner) => this.owner = owner; @@ -150,13 +175,12 @@ private static void CancelRunningGenerate(AsyncTaskBase? task) task.IsCanceled = true; } - private static void WaitForPendingGenerate(ref AsyncTaskBase? task) + private static void WaitForPendingGenerate(AsyncTaskBase? task) { if (task == null) return; task.WaitForCompletion(); task.Dispose(); - task = null; } #endregion @@ -177,214 +201,202 @@ public void Dispose() #region Internal Methods - internal (Image, InterpolationMode) GetDisplayImage() + internal void InvalidateImages() { - #region Local Methods - - void SetOrGenerateDefaultDisplayImage() - { - Debug.Assert(owner.image != null && owner.pixelFormat != default); - if (!enabled) - return; - - Image image = owner.image!; - Bitmap? bitmap = image as Bitmap; - - // Metafile: The default image is the same as the original. If anti-aliased images are required, a clone is created on demand from that task - // Bitmap: generating a new default image for unsupported formats, - bool isGenerateNeeded = bitmap != null && (owner.pixelFormat.In(unsupportedFormats) - // non-PARGB32 images larger than 256x256 - note: leaving even slow formats unconverted below sizeThreshold / 4 - || owner.pixelFormat != PixelFormat.Format32bppPArgb && (owner.imageSize.Width > sizeThreshold >> 2 || owner.imageSize.Height > sizeThreshold >> 2) - // Raw icons: converting because icons are handled oddly by GDI+, for example, the first column has half pixel width - || bitmap.RawFormat.Guid == ImageFormat.Icon.Guid); - - if (!isGenerateNeeded) - { - // A generated default image is set from another thread so handling possible concurrency. - if (defaultDisplayImage == null) - Interlocked.CompareExchange(ref defaultDisplayImage, image, null); - return; - } - - // We check if there is a generate in progress. If not, then we start a new one (if it has just been finished). - AsyncTaskBase? task = generateDefaultImageTask; - if (task != null) - return; + // This cancels all tasks and disposes every generating resources + Free(); + Debug.Assert(generateDefaultImageTask?.IsCanceled != false && generateResizedImageTask?.IsCanceled != false); - task = new GenerateDefaultImageTask - { - SourceBitmap = bitmap!, - InvalidateOwner = bitmap!.RawFormat.Guid == ImageFormat.Icon.Guid - }; - generateDefaultImageTask = task; - ThreadPool.QueueUserWorkItem(GenerateDefaultImage, task); - } + // (Re-)enabling generating images + enabled = true; + } - #endregion + internal void InvalidateDisplayImage() + { + // Just canceling possible running generate. Not even clearing the possible already generated image. + // A new task will be started if a new paint explicitly requires it, in which case the last image can be re-used if possible. + CancelRunningGenerate(generateResizedImageTask); + } + internal (Image?, InterpolationMode) GetDisplayImage() + { Debug.Assert(owner.image != null); InterpolationMode interpolationMode = InterpolationMode.NearestNeighbor; - // 1.) There is a size adjusted display image - Image? result = adjustedDisplayImage; - if (result != null) - return (result, interpolationMode); + // 1.) Returning with a size adjusted display image + if (owner.smoothZooming && resizedDisplayImageSize == owner.targetRectangle.Size) + return (resizedDisplayImage, interpolationMode); - // 2.) Checking if there is an already available default image. It might have to be resized by painting. - result = defaultDisplayImage; + // 2.) Checking if there is an already available default image. It might have to be resized on painting. + Image? result = defaultDisplayImage; - // Smoothing Bitmap: leaving NearestNeighbor if an adjusted image will be generated; otherwise, using some interpolation to be applied during painting + // Smoothing Bitmap: leaving NearestNeighbor if a resized image is expected to be generated; + // otherwise, using some interpolation to be applied during painting if (!owner.isMetafile && owner.smoothZooming) { float zoom = owner.zoom; Size size = owner.imageSize; - // Large zoom or shrunk image smaller than generating threshold: using HighQualityBicubic + // >4x zoom or shrunk image that is not greater than generating threshold: using HighQualityBicubic if (zoom >= 4f || zoom < 1f && size.Width <= sizeThreshold && size.Height <= sizeThreshold) interpolationMode = InterpolationMode.HighQualityBicubic; - // Small zoom: HighQualityBilinear for large images to prevent heavy lagging; otherwise, HighQualityBicubic + // 1-4x zoom: HighQualityBilinear for large images to prevent heavy lagging; otherwise, HighQualityBicubic else if (zoom > 1f) interpolationMode = size.Width > sizeThreshold || size.Height > sizeThreshold ? InterpolationMode.HighQualityBilinear : InterpolationMode.HighQualityBicubic; - // Shrinking larger images but generating is disabled: applying a fallback interpolation + // Shrinking of larger images if generating is disabled: applying a hopefully-not-too-slow fallback interpolation else if (!enabled && zoom < 1f) interpolationMode = owner.targetRectangle.Width > sizeThreshold || owner.targetRectangle.Height > sizeThreshold ? InterpolationMode.Bilinear : InterpolationMode.Bicubic; } + // 3.) Starting to generate cached images if needed + if (enabled) + { + if (result == null) + BeginGenerateDefaultDisplayImageIfNeeded(); + + BeginGenerateResizedDisplayImageIfNeeded(); + } + + // 4.) Returning either a generated display or the original image if (result != null) + // here we already have a default display image we can return with return (result, interpolationMode); - SetOrGenerateDefaultDisplayImage(); - - // Waiting for the task to be finished if pixel format is not supported, - // or it is so slow (>= 48bpp) that it is faster to wait for the converted image and paint that one. - // Note: Not using async because this project targets also .NET 3.5 and the image is locked anyway + // Waiting for the display image to be generated if pixel format is not supported, + // or it is so slow (>= 48bpp) that it is faster to wait for the converted image and paint the existing one. + // Note: Not using async because this project targets also .NET 3.5 and the image is locked anyway also in paint if (owner.pixelFormat.In(unsupportedFormats) || owner.pixelFormat.In(slowFormats)) generateDefaultImageTask?.WaitForCompletion(); - // 3.) Returning possibly generated display image or falling back to the original image + // Too low memory: turning off image generation and freeing up resources. if (!enabled) { Free(); - // So next time we will return sooner, at 2.) - return (defaultDisplayImage = owner.image!, interpolationMode); + // Assigning by original image to defaultDisplayImage so even large >= 48bpp images will be drawn directly. + defaultDisplayImage = owner.image; } - return (defaultDisplayImage ?? owner.image!, interpolationMode); + // Unless a default image has been generated in the meantime we return with the original image, or null, if its pixel format is not supported. + result = defaultDisplayImage ?? owner.image; + if (ReferenceEquals(result, owner.image) && owner.pixelFormat.In(unsupportedFormats)) + result = null; + + return (result, interpolationMode); } - /// - /// Invalidates everything and resets the default image if it is not needed to be generated. - /// - internal void InvalidateImages() + #endregion + + #region Private Methods + + private void Free() { - // This cancels all tasks and disposes every generating resources - Free(); - Debug.Assert(generateDefaultImageTask == null); + // disabling to prevent starting new tasks while freeing resources + enabled = false; + CancelRunningGenerate(generateDefaultImageTask); + CancelRunningGenerate(generateResizedImageTask); - // (Re-)enabling generating images - enabled = true; + WaitForPendingGenerate(generateDefaultImageTask); + WaitForPendingGenerate(generateResizedImageTask); - // TODO - //Image? image = owner.image; - //if (image == null) - // return; - - //Debug.Assert(owner.pixelFormat != default, "PixelFormat must be known if image is not null"); - //// Metafile: no generating needed, the default image is the same as the original - //// (for Metafiles a clone is created on demand is anti-aliased images have to be created to prevent locking on the displayed image) - //if (image is not Bitmap bitmap) - //{ - // defaultImage = image; - // return; - //} - - //// Bitmap: generating a new default image for unsupported formats, - //bool isGenerateNeeded = owner.pixelFormat.In(unsupportedFormats) - // // non-PARGB32 images larger than 256x256 - note: leaving even slow formats unconverted below sizeThreshold / 4 - // || owner.pixelFormat != PixelFormat.Format32bppPArgb && (owner.imageSize.Width > sizeThreshold >> 2 || owner.imageSize.Height > sizeThreshold >> 2) - // // Raw icons: converting because icons are handled oddly by GDI+, for example, the first column has half pixel width - // || bitmap.RawFormat.Guid == ImageFormat.Icon.Guid; - - //// If generate is needed, then leaving defaultImage null and starting a generate when an actual Paint is triggered. - //// Starting the task here would just be slower anyway because it locks the image, which wouldn't let the Paint draw the original image until it ends. - //if (!isGenerateNeeded) - // defaultImage = image; + requestedSize = default; + + lock (SyncRoot) + { + if (isDefaultImageCloned) + defaultDisplayImage?.Dispose(); + defaultDisplayImage = null; + isDefaultImageCloned = false; + + resizedDisplayImageSize = default; + resizedDisplayImage?.Dispose(); + resizedDisplayImage = null; + } } - /// - /// Invalidates the display image and if required, starts to generate a new one - /// - internal void InvalidateDisplayImage() + private void BeginGenerateDefaultDisplayImageIfNeeded() { - // todo: mainly the old BeginGenerateDisplayImage - // TODO: handle possible defaultImage generation - } + Debug.Assert(owner.image != null && owner.pixelFormat != default); - // TODO - //internal void BeginGenerateDisplayImage() - //{ - // CancelRunningGenerate(); - // if (!enabled) - // return; - - // // TODO - // //Image? image = owner.image; - // //if (image == null) - // //{ - // // Debug.Assert(cachedDisplayImage == null && displayImage == null); - // // return; - // //} - - // //Size size = owner.targetRectangle.Size; - // //bool isGenerateNeeded = owner.isMetafile - // // ? owner.smoothZooming - // // : owner.smoothZooming && owner.zoom < 1f && (owner.imageSize.Width > sizeThreshold || owner.imageSize.Height > sizeThreshold); - - // //if (!isGenerateNeeded || size.Width < 1 || size.Height < 1) - // //{ - // // displayImage = safeDefaultImage; - // // return; - // //} - - // //requestedSize = size; - // //ThreadPool.QueueUserWorkItem(DoGenerate!, new GenerateTask { SourceImage = sourceClone, Size = size }); - //} + // A task is already running or the display image is already generated. + if (isDefaultImageCloned || generateDefaultImageTask != null) + return; - #endregion + Image image = owner.image!; + Bitmap? bitmap = image as Bitmap; - #region Private Methods + // Metafile: The default image is the same as the original. If anti-aliased images are required, a clone is created on demand from that task + // Bitmap: generating a new default image for unsupported formats, + bool isGenerateNeeded = bitmap != null && (owner.pixelFormat.In(unsupportedFormats) + // for non-PARGB32 images larger than 256x256 - note: leaving even slow formats unconverted below sizeThreshold / 4 + || owner.pixelFormat != PixelFormat.Format32bppPArgb && (owner.imageSize.Width > sizeThreshold >> 2 || owner.imageSize.Height > sizeThreshold >> 2) + // and for native icons: converting because icons are handled oddly by GDI+, for example, the first column has half pixel width + || bitmap.RawFormat.Guid == ImageFormat.Icon.Guid); - private void Free() - { - // to prevent starting new tasks while freeing resources - enabled = false; - CancelRunningGenerate(generateDefaultImageTask); - CancelRunningGenerate(generateDisplayImageTask); + if (!isGenerateNeeded) + { + // A generated default image is set from another thread so handling possible concurrency. + if (defaultDisplayImage == null) + Interlocked.CompareExchange(ref defaultDisplayImage, image, null); + return; + } - lock (syncRootGenerate) + var task = new GenerateDefaultImageTask { - WaitForPendingGenerate(ref generateDefaultImageTask); - WaitForPendingGenerate(ref generateDisplayImageTask); + SourceBitmap = bitmap!, + InvalidateOwner = bitmap!.RawFormat.Guid == ImageFormat.Icon.Guid + }; + generateDefaultImageTask = task; + ThreadPool.QueueUserWorkItem(GenerateDefaultImage, task); + } - // TODO - //requestedSize = default; - //displayImage = null; - //sourceClone?.Dispose(); - //sourceClone = null; + private void BeginGenerateResizedDisplayImageIfNeeded() + { + Debug.Assert(owner.image != null && owner.pixelFormat != default); + + // Metafile: If smoothing edges is enabled + // Bitmap: If smoothing resize is enabled, the image is shrunk and image size is larger than 1024x1024 + bool isGenerateNeeded = owner.smoothZooming && (owner.isMetafile + || owner.zoom < 1f && (owner.imageSize.Width > sizeThreshold || owner.imageSize.Height > sizeThreshold)); + + // Not canceling the possible generate task here. It will call an invalidate in the end and we can see whether we use the result. + Size size = owner.targetRectangle.Size; + if (!isGenerateNeeded || size.Width < 1 || size.Height < 1) + { + requestedSize = default; + return; + } - // ImageViewer also locks on the private generator when obtains display image so this ensures that no disposed image is painted. - lock (this) + requestedSize = size; + GenerateResizedImageTask? task = generateResizedImageTask; + if (task != null) + { + // If there is already a running generate task + if (!task.IsCanceled) { - if (isDefaultImageCloned) - defaultDisplayImage?.Dispose(); - defaultDisplayImage = null; - isDefaultImageCloned = false; + // It is generating the same size: we keep it + if (task.Size == size) + return; + + // We just initiate cancellation but not awaiting the completion. + task.IsCanceled = true; } - // TODO - //FreeCachedPreview(); + // We do not await the task (we are in a lock here that is used in the task, too). + // Instead, we invalidate the owner so another paint will be triggered some time later. Hopefully the task will have been finished by that time. + owner.Invalidate(); + return; } + + Debug.Assert(generateResizedImageTask == null); + task = new GenerateResizedImageTask + { + SourceImage = owner.image!, + Size = size + }; + + generateResizedImageTask = task; + ThreadPool.QueueUserWorkItem(GenerateResizedImage, task); } private void GenerateDefaultImage(object state) @@ -399,12 +411,8 @@ private void GenerateDefaultImage(object state) // x4: because we want to convert to 32bpp long managedPressure = size.Width * size.Height * 4; if (!MemoryHelper.CanAllocate(managedPressure) && !task.SourceBitmap.PixelFormat.In(unsupportedFormats)) - { - Debug.WriteLine($"Discarding task because there is no {managedPressure:N0} bytes of available managed memory"); task.IsCanceled = true; - } - Debug.WriteLine($"Generating fast default image on thread #{Thread.CurrentThread.ManagedThreadId}"); Bitmap? result = null; try { @@ -418,21 +426,22 @@ private void GenerateDefaultImage(object state) // Not using Task and await because we want to be compatible with .NET 3.5, too. IAsyncResult asyncResult = src.BeginCopyTo(dst, asyncConfig: cfg); - // As we are already on a pool thread the End... call does not block the UI. + // As we are already on a pool thread the End... call does not block the UI. It's still not the same as CopyTo() due to cancellation support. asyncResult.EndCopyTo(); } - catch (Exception e) when (!e.IsCriticalGdi()) + catch (Exception e) { // Despite all of the preconditions the memory could not be allocated or some other error occurred (yes, we catch even OutOfMemoryException here) // NOTE: practically we always can recover from here: we simply don't use a generated clone and the worker thread can be finished task.IsCanceled = true; enabled = false; + if (e.IsCriticalGdi()) + throw; } finally { if (task.IsCanceled) { - Debug.WriteLine("Canceled default image generate"); result?.Dispose(); result = null; } @@ -445,310 +454,250 @@ private void GenerateDefaultImage(object state) var task = (GenerateDefaultImageTask)state; - // ensuring that only one generate task is running at once - lock (syncRootGenerate) + try { - // lost race - if (!enabled || task.SourceBitmap != owner.image || disposed) + // canceled or lost race + if (task.IsCanceled || isDefaultImageCloned || task.SourceBitmap != owner.image || !enabled || disposed) + return; + + Bitmap? result = null; + + // Locking on the image to avoid the possible "bitmap region is already locked" issue. + // Until the default image is generated, it is locked during the paint, too. + lock (task.SourceBitmap) { - task.Dispose(); + try + { + // Generating the actual result. IsCanceled might be true if the lock above could not be immediately acquired + if (!task.IsCanceled) + result = DoGenerateDefaultImage(task, ref enabled); + } + finally + { + task.SetCompleted(); + } + } + + if (result == null || task.IsCanceled) return; + + defaultDisplayImage = result; + isDefaultImageCloned = true; + + // only for icons because otherwise the appearance is the same + if (task.InvalidateOwner) + owner.Invalidate(); + } + finally + { + task.Dispose(); + generateDefaultImageTask = null; + } + } + + private void GenerateResizedImage(object state) + { + #region Local Methods + + static Bitmap? GenerateResizedMetafile(GenerateResizedImageTask task, ref bool enabled) + { + // For the resizing large managed buffer of source.Height * target.Width of ColorF (16 bytes) is allocated internally. To be safe we count with the doubled sizes. + Size doubledSize = new Size(task.Size.Width << 1, task.Size.Height << 1); + long managedPressure = doubledSize.Width * doubledSize.Height * 16; + if (!MemoryHelper.CanAllocate(managedPressure)) + task.IsCanceled = true; + + if (task.IsCanceled) + return null; + + // MetafileExtensions.ToBitmap does the same if anti aliasing is requested but this way the process can be canceled + Bitmap? result = null; + Bitmap? doubled = null; + try + { + doubled = new Bitmap(task.SourceImage, task.Size.Width << 1, task.Size.Height << 1); + + if (!task.IsCanceled) + { + result = new Bitmap(task.Size.Width, task.Size.Height, PixelFormat.Format32bppPArgb); + using IReadableBitmapData src = doubled.GetReadableBitmapData(); + using IReadWriteBitmapData dst = result.GetReadWriteBitmapData(); + + // not using Task and await we want to be compatible with .NET 3.5 + IAsyncResult asyncResult = src.BeginDrawInto(dst, new Rectangle(Point.Empty, doubled.Size), new Rectangle(Point.Empty, result.Size), + asyncConfig: new AsyncConfig { IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false }); + + // As we are already on a pool thread this is not a UI blocking call + asyncResult.EndDrawInto(); + } + } + catch (Exception e) + { + // Despite all of the preconditions the memory could not be allocated or some other error occurred (yes, we catch even OutOfMemoryException here) + // NOTE: practically we always can recover from here: we simply don't use a generated preview and the worker thread can be finished + task.IsCanceled = true; + enabled = false; + if (e.IsCriticalGdi()) + throw; + } + finally + { + doubled?.Dispose(); + if (task.IsCanceled) + { + result?.Dispose(); + result = null; + } } - // from now on the task can be canceled - generateDefaultImageTask = task; + return result; + } + static Bitmap? GenerateResizedBitmap(GenerateResizedImageTask task, ref bool enabled) + { + // BitmapExtensions.Resize does the same but this way the process can be canceled + Bitmap? result = null; try { - Bitmap? result = null; + result = new Bitmap(task.Size.Width, task.Size.Height, PixelFormat.Format32bppPArgb); + lock (task.SourceImage) + { + using IReadableBitmapData src = ((Bitmap)task.SourceImage).GetReadableBitmapData(); + using IReadWriteBitmapData dst = result.GetReadWriteBitmapData(); + var cfg = new AsyncConfig { IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false, MaxDegreeOfParallelism = Environment.ProcessorCount >> 1 }; + + // not using Task and await we want to be compatible with .NET 3.5 + // As we are already on a pool thread the End... call does not block the UI. + var srcRect = new Rectangle(Point.Empty, task.SourceImage!.Size); + var dstRect = new Rectangle(Point.Empty, task.Size); + if (srcRect == dstRect) + { + IAsyncResult asyncResult = src.BeginCopyTo(dst, srcRect, Point.Empty, asyncConfig: cfg); + asyncResult.EndCopyTo(); + } + else + { + IAsyncResult asyncResult = src.BeginDrawInto(dst, srcRect, dstRect, asyncConfig: cfg); + asyncResult.EndDrawInto(); + } + } + } + catch (Exception e) + { + // Despite all of the preconditions the memory could not be allocated or some other error occurred (yes, we catch even OutOfMemoryException here) + // NOTE: practically we always can recover from here: we simply don't use a generated preview and the worker thread can be finished + task.IsCanceled = true; + enabled = false; + if (e.IsCriticalGdi()) + throw; + } + finally + { + if (task.IsCanceled) + { + result?.Dispose(); + result = null; + } + } - // unless already replaced, this is the same as the owner.image so locking on it to avoid the possible "bitmap region is already locked" issue. - lock (task.SourceBitmap) + return result; + } + + #endregion + + var task = (GenerateResizedImageTask)state; + + try + { + // canceled or lost race + if (task.IsCanceled || task.SourceImage != owner.image || task.Size != requestedSize || !enabled || disposed) + return; + + // returning if we already have the result + if (task.Size == resizedDisplayImageSize) + { + owner.Invalidate(); + return; + } + + // Before creating the preview releasing previous cached result. It is important to free it here, before checking the free memory. + // The lock ensures that no disposed image is displayed + lock (SyncRoot) + { + resizedDisplayImageSize = default; + resizedDisplayImage?.Dispose(); + resizedDisplayImage = null; + } + + // 1.) If there is no cloned display image generating that one first so the UI can use that while the original image will be free to create the resized images from. + if (!isDefaultImageCloned) + { + // The clone is just being generated. Invalidating and returning to come back later. + if (defaultDisplayImage == null || generateDefaultImageTask != null) + { + owner.Invalidate(); + return; + } + + Debug.Assert(ReferenceEquals(owner.image, defaultDisplayImage), "If isDefaultImageCloned is false, then defaultDisplayImage is expected to be the original instance here."); + Debug.Assert(owner.isMetafile || owner.pixelFormat == PixelFormat.Format32bppPArgb, "Clone is expected to be missing for metafiles and 32bpp PARGB bitmaps only."); + Image clone; + + // This may block the UI in OnPaint but once the clone is created OnPaint will use that instead of the original image. + lock (task.SourceImage) { try { - // Generating the actual result. IsCanceled might be true if the lock above could not be immediately acquired - if (!task.IsCanceled) - result = DoGenerateDefaultImage(task, ref enabled); + // we do not allow canceling this part because this would be started again and again + clone = owner.image is Bitmap bitmap + ? bitmap.ConvertPixelFormat(PixelFormat.Format32bppPArgb) + : (Image)owner.image.Clone(); } - finally + catch (Exception) { - task.SetCompleted(); + enabled = false; + return; } } - if (result == null || task.IsCanceled) - return; - - defaultDisplayImage = result; + defaultDisplayImage = clone; isDefaultImageCloned = true; + } - // only for icons because otherwise the appearance is the same - if (task.InvalidateOwner) - owner.Invalidate(); + if (task.IsCanceled) + return; + + // 2.) Generating the size-adjusted display image + Bitmap? result = null; + try + { + if (!task.IsCanceled) + result = task.SourceImage is Metafile ? GenerateResizedMetafile(task, ref enabled) : GenerateResizedBitmap(task, ref enabled); } finally { - task.Dispose(); - generateDefaultImageTask = null; + task.SetCompleted(); + } + + // setting latest cache (even if the task has been canceled as we have a completed result) + if (result != null) + { + resizedDisplayImage = result; + resizedDisplayImageSize = task.Size; } + + if (task.IsCanceled) + return; + + owner.Invalidate(); + } + finally + { + task.Dispose(); + generateResizedImageTask = null; } } - // TODO - //private bool TrySetPreview(Image? reference, Size size) - //{ - // if (sourceClone != null && reference != sourceClone) - // { - // Debug.Assert(cachedDisplayImage != displayImage, "If image has been replaced in owner, its display image is not expected to be cached here"); - // FreeCachedPreview(); - // return false; - // } - - // // we don't free generated preview here maybe it can be re-used later (eg. toggling metafile smooth zooming) - // if (currentCachedDisplayImage != size) - // return false; - - // if (displayImage == cachedDisplayImage) - // return true; - - // Debug.WriteLine($"Re-using pregenerated preview of size {size.Width}x{size.Height}"); - // displayImage = cachedDisplayImage; - // owner.Invalidate(); - // return true; - //} - - //private void FreeCachedPreview() - //{ - // lock (this) // It is alright, this is a private class. ImageViewer also locks on this instance when obtains display image so this ensures that no disposed image is painted. - // { - // if (displayImage != null && displayImage == cachedDisplayImage) - // { - // displayImage = null; - // owner.Invalidate(); - // } - - // Bitmap? toFree = cachedDisplayImage; - // cachedDisplayImage = null; - // toFree?.Dispose(); - // currentCachedDisplayImage = default; - // } - //} - - //private void DoGenerate(object state) - //{ - // var task = (GenerateTask)state; - - // // this is a fairly large lock ensuring that only one generate task is running at once - // lock (syncRootGenerate) - // { - // // lost race - // if (!enabled || task.SourceImage != sourceClone || task.Size != requestedSize) - // { - // task.Dispose(); - // return; - // } - - // // checking if we already have the preview - // if (!task.IsCanceled) - // { - // if (TrySetPreview(task.SourceImage, task.Size)) - // { - // task.Dispose(); - // return; - // } - - // // Before creating the preview releasing previous cached result. It is important to free it here, before checking the free memory. - // FreeCachedPreview(); - // } - - // if (task.SourceImage == null) - // { - // Debug.Assert(sourceClone == null && owner.image != null); - // Image image = owner.image!; - - // // As OnPaint can occur any time in the UI thread we lock on it. See also PaintImage. - // lock (image) - // { - // try - // { - // // A clone must be created to use the image without locking later on and getting an "object is used elsewhere" error from paint. - // // This is created synchronously so it can be used as a reference in the generating tasks. - // if (owner.isMetafile) - // sourceClone = (Metafile)image.Clone(); - // else - // { - // PixelFormat pixelFormat = image.PixelFormat; - // var bmp = (Bitmap)image; - - // // clone is tried to be compact, fast and compatible - // sourceClone = pixelFormat.In(PixelFormat.Format32bppArgb, PixelFormat.Format32bppPArgb, PixelFormat.Format64bppArgb, PixelFormat.Format64bppPArgb) ? bmp.ConvertPixelFormat(PixelFormat.Format32bppPArgb) - // : pixelFormat.In(PixelFormat.Format24bppRgb, PixelFormat.Format32bppRgb, PixelFormat.Format48bppRgb) ? bmp.ConvertPixelFormat(PixelFormat.Format24bppRgb) - // : pixelFormat == PixelFormat.Format16bppGrayScale ? bmp.ConvertPixelFormat(PixelFormat.Format8bppIndexed, PredefinedColorsQuantizer.Grayscale()) - // : pixelFormat.In(convertedFormats) ? bmp.ConvertPixelFormat(PixelFormat.Format32bppPArgb) - // : bmp.CloneCurrentFrame(); - // } - - // task.SourceImage = sourceClone; - // } - // catch (Exception e) when (!e.IsCriticalGdi()) - // { - // // Disabling preview generation if we could not create the clone (eg. on low memory) - // // It will be re-enabled when owner.Image is reset. - // enabled = false; - // sourceClone?.Dispose(); - // sourceClone = null; - // return; - // } - // } - // } - - // Debug.Assert(activeTask?.IsCanceled != false); - // WaitForPendingGenerate(); - // Debug.Assert(activeTask == null); - - // // from now on the task can be canceled - // activeTask = task; - - // try - // { - // Bitmap? result = null; - // try - // { - // if (!task.IsCanceled) - // result = task.SourceImage is Metafile ? GenerateMetafilePreview(task) : GenerateBitmapPreview(task); - // } - // finally - // { - // task.SetCompleted(); - // } - - // if (result != null) - // { - // // setting latest cache (even if the task has been canceled since the generating the completed result) - // currentCachedDisplayImage = task.Size; - // cachedDisplayImage = result; - // } - - // if (task.IsCanceled) - // return; - - // Debug.WriteLine("Applying generated result"); - // Debug.Assert(displayImage == null || displayImage == safeDefaultImage || displayImage == owner.image, "Display image is not the same as the original one: dispose is necessary"); - - // // not freeing the display image because it is always the original image here - // displayImage = result; - // owner.Invalidate(); - // } - // finally - // { - // task.Dispose(); - // activeTask = null; - // } - // } - //} - - //private static Bitmap? GenerateMetafilePreview(GenerateTask task) - //{ - // // For the resizing large managed buffer of source.Height * target.Width of ColorF (16 bytes) is allocated internally. To be safe we count with the doubled sizes. - // Size doubledSize = new Size(task.Size.Width << 1, task.Size.Height << 1); - // long managedPressure = doubledSize.Width * doubledSize.Height * 16; - // if (!MemoryHelper.CanAllocate(managedPressure)) - // { - // Debug.WriteLine($"Discarding task because there is no {managedPressure:N0} bytes of available managed memory"); - // task.IsCanceled = true; - // } - - // if (task.IsCanceled) - // return null; - // // MetafileExtensions.ToBitmap does the same if anti aliasing is requested but this way the process can be canceled - // Debug.WriteLine($"Generating anti aliased image {task.Size.Width}x{task.Size.Height} on thread #{Thread.CurrentThread.ManagedThreadId}"); - // Bitmap? result = null; - // Bitmap? doubled = null; - // try - // { - // doubled = new Bitmap(task.SourceImage!, task.Size.Width << 1, task.Size.Height << 1); - // if (!task.IsCanceled) - // { - // result = new Bitmap(task.Size.Width, task.Size.Height, PixelFormat.Format32bppPArgb); - // using IReadableBitmapData src = doubled.GetReadableBitmapData(); - // using IReadWriteBitmapData dst = result.GetReadWriteBitmapData(); - - // // not using Task and await, because this method's signature must match the WaitCallback delegate, and we want to be compatible with .NET 3.5, too - // IAsyncResult asyncResult = src.BeginDrawInto(dst, new Rectangle(Point.Empty, doubled.Size), new Rectangle(Point.Empty, result.Size), - // // ReSharper disable once AccessToModifiedClosure - intended, if IsCanceled is modified we need to return its modified value - // asyncConfig: new AsyncConfig { IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false }); - - // // As we are already on a pool thread this is not a UI blocking call - // // This will throw an exception if resizing failed (resizing also allocates a large amount of memory). - // asyncResult.EndDrawInto(); - // } - // } - // catch (Exception e) when (!e.IsCriticalGdi()) - // { - // // Despite all of the preconditions the memory could not be allocated or some other error occurred (yes, we catch even OutOfMemoryException here) - // // NOTE: practically we always can recover from here: we simply don't use a generated preview and the worker thread can be finished - // task.IsCanceled = true; - // } - // finally - // { - // doubled?.Dispose(); - // if (task.IsCanceled) - // { - // result?.Dispose(); - // result = null; - // } - // } - - // return result; - //} - - //private static Bitmap? GenerateBitmapPreview(GenerateTask task) - //{ - // // BitmapExtensions.Resize does the same but this way the process can be canceled - // Debug.WriteLine($"Generating smoothed image {task.Size.Width}x{task.Size.Height} on thread #{Thread.CurrentThread.ManagedThreadId}"); - - // Bitmap? result = null; - // try - // { - // result = new Bitmap(task.Size.Width, task.Size.Height, PixelFormat.Format32bppPArgb); - // using IReadableBitmapData src = ((Bitmap)task.SourceImage!).GetReadableBitmapData(); - // using IReadWriteBitmapData dst = result.GetReadWriteBitmapData(); - // var cfg = new AsyncConfig { IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false, MaxDegreeOfParallelism = Environment.ProcessorCount >> 1 }; - - // // Not using Task and await, because this method's signature must match the WaitCallback delegate, and we want to be compatible with .NET 3.5, too. - // // As we are already on a pool thread the End... call does not block the UI. - // var srcRect = new Rectangle(Point.Empty, task.SourceImage!.Size); - // var dstRect = new Rectangle(Point.Empty, task.Size); - // if (srcRect == dstRect) - // { - // IAsyncResult asyncResult = src.BeginCopyTo(dst, srcRect, Point.Empty, asyncConfig: cfg); - // asyncResult.EndCopyTo(); - // } - // else - // { - // IAsyncResult asyncResult = src.BeginDrawInto(dst, srcRect, dstRect, asyncConfig: cfg); - // asyncResult.EndDrawInto(); - // } - // } - // catch (Exception e) when (!e.IsCriticalGdi()) - // { - // // Despite all of the preconditions the memory could not be allocated or some other error occurred (yes, we catch even OutOfMemoryException here) - // // NOTE: practically we always can recover from here: we simply don't use a generated preview and the worker thread can be finished - // task.IsCanceled = true; - // } - // finally - // { - // if (task.IsCanceled) - // { - // result?.Dispose(); - // result = null; - // } - // } - - // return result; - //} - #endregion #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 53ee115..7104ee1 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -400,9 +400,7 @@ private void Invalidate(InvalidateFlags flags) if ((flags & InvalidateFlags.Image) != InvalidateFlags.None) displayImageGenerator.InvalidateImages(); - - // this relies on the new calculated sizes so must come after adjusting sizes - if ((flags & InvalidateFlags.DisplayImage) != InvalidateFlags.None) + else if ((flags & InvalidateFlags.DisplayImage) != InvalidateFlags.None) displayImageGenerator.InvalidateDisplayImage(); Invalidate(); @@ -532,15 +530,20 @@ private void PaintImage(Graphics g) if (sbVerticalVisible) dest.Y -= sbVertical.Value; - // This lock ensures that no disposed image is painted. The generator also locks on itself when frees the cached preview. - lock (displayImageGenerator) + // This lock ensures that no disposed image is painted. The generator also locks on it when frees the cached preview. + lock (displayImageGenerator.SyncRoot) { - (Image toDraw, InterpolationMode interpolationMode) = displayImageGenerator.GetDisplayImage(); + (Image? toDraw, InterpolationMode interpolationMode) = displayImageGenerator.GetDisplayImage(); + + // happens if image format is not supported and generating compatible display images is disabled due to low memory + if (toDraw == null) + return; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.InterpolationMode = interpolationMode; // Locking on display image so if it is the same as the original image, which is also locked when accessing its bitmap data - // the "bitmap region is already locked" can be avoided. Important: this cannot be ensured without locking here internally because + // so the "bitmap region is already locked" can be avoided. Important: this cannot be ensured without locking here internally because // OnPaint can occur any time after invalidating. bool useLock = image == toDraw; if (useLock) diff --git a/changelog.txt b/changelog.txt index 895b68d..bcbbe79 100644 --- a/changelog.txt +++ b/changelog.txt @@ -22,7 +22,9 @@ + Color visualizer form: OK/Cancel buttons + Image visualizer form: OK/Cancel buttons * Manual zooming is enabled even if Auto Zoom is on; this turns off Auto Zoom automatically. -* Turning smoothing of zoomed images on/off affects also shrunk images (previously affected enlarged images only). +* ImageViewer control: + * Turning on/off smoothing of zoomed images affects also shrunk images (previously affected enlarged images only). + * Improving performance of some image formats. - Fixing scaling of menu item images under Linux/Mono when using high DPI - Color Count: Result was not always shown (and progress bar was not removed) if the operation ended very quickly. - Resize: From 309e95681ecda9f5d8c3e398806e62fac69538a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 16 Jun 2021 13:59:36 +0200 Subject: [PATCH 119/211] Tooltips for color trackbar --- .../KGySoft.Drawing.ImagingTools.Messages.resx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 1bc81b2..adae042 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -198,6 +198,18 @@ B: {0} + + Alpha color component (transparency) + + + Red color component + + + Green color component + + + Blue color component + Unsupported Language ({0}) @@ -497,7 +509,7 @@ It is possible that is has no effect. By selecting a quantizer you can specify a different back color. - This item is for a different ImagingTools version. + This item is for a different Imaging Tools version. A quantizer has been auto selected for pixel format '{0}' using default settings. @@ -652,7 +664,7 @@ Copyright © {2} KGy SOFT. All rights reserved. Open Image - Save Image As... + Save Image As Default From 76e133a3c31b80b558562c8406d47c08873286e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 16 Jun 2021 14:00:22 +0200 Subject: [PATCH 120/211] Analyzer issues --- KGySoft.Drawing.Tools.sln.DotSettings | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.Tools.sln.DotSettings b/KGySoft.Drawing.Tools.sln.DotSettings index 620f173..41c7464 100644 --- a/KGySoft.Drawing.Tools.sln.DotSettings +++ b/KGySoft.Drawing.Tools.sln.DotSettings @@ -28,5 +28,8 @@ True True True + True + True True - True \ No newline at end of file + True + True \ No newline at end of file From 2ae3d6e7ff7a2158d5b30a9248cc340b16ebcad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 16 Jun 2021 16:20:42 +0200 Subject: [PATCH 121/211] Optimizing ImageViewer.DisplayImageGenerator --- .../ImageViewer.DisplayImageGenerator.cs | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs index 52a11f8..c103c30 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs @@ -429,14 +429,12 @@ private void GenerateDefaultImage(object state) // As we are already on a pool thread the End... call does not block the UI. It's still not the same as CopyTo() due to cancellation support. asyncResult.EndCopyTo(); } - catch (Exception e) + catch (Exception) { // Despite all of the preconditions the memory could not be allocated or some other error occurred (yes, we catch even OutOfMemoryException here) // NOTE: practically we always can recover from here: we simply don't use a generated clone and the worker thread can be finished task.IsCanceled = true; enabled = false; - if (e.IsCriticalGdi()) - throw; } finally { @@ -524,21 +522,26 @@ private void GenerateResizedImage(object state) using IReadWriteBitmapData dst = result.GetReadWriteBitmapData(); // not using Task and await we want to be compatible with .NET 3.5 - IAsyncResult asyncResult = src.BeginDrawInto(dst, new Rectangle(Point.Empty, doubled.Size), new Rectangle(Point.Empty, result.Size), - asyncConfig: new AsyncConfig { IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false }); + IAsyncResult asyncResult = src.BeginDrawInto(dst, + new Rectangle(Point.Empty, doubled.Size), + new Rectangle(Point.Empty, task.Size), + asyncConfig: new AsyncConfig + { + IsCancelRequestedCallback = () => task.IsCanceled, + ThrowIfCanceled = false, + MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount - 2) + }); // As we are already on a pool thread this is not a UI blocking call asyncResult.EndDrawInto(); } } - catch (Exception e) + catch (Exception) { // Despite all of the preconditions the memory could not be allocated or some other error occurred (yes, we catch even OutOfMemoryException here) // NOTE: practically we always can recover from here: we simply don't use a generated preview and the worker thread can be finished task.IsCanceled = true; enabled = false; - if (e.IsCriticalGdi()) - throw; } finally { @@ -564,32 +567,28 @@ private void GenerateResizedImage(object state) { using IReadableBitmapData src = ((Bitmap)task.SourceImage).GetReadableBitmapData(); using IReadWriteBitmapData dst = result.GetReadWriteBitmapData(); - var cfg = new AsyncConfig { IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false, MaxDegreeOfParallelism = Environment.ProcessorCount >> 1 }; // not using Task and await we want to be compatible with .NET 3.5 + IAsyncResult asyncResult = src.BeginDrawInto(dst, + new Rectangle(Point.Empty, task.SourceImage!.Size), + new Rectangle(Point.Empty, task.Size), + asyncConfig: new AsyncConfig + { + IsCancelRequestedCallback = () => task.IsCanceled, + ThrowIfCanceled = false, + MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount - 2) + }); + // As we are already on a pool thread the End... call does not block the UI. - var srcRect = new Rectangle(Point.Empty, task.SourceImage!.Size); - var dstRect = new Rectangle(Point.Empty, task.Size); - if (srcRect == dstRect) - { - IAsyncResult asyncResult = src.BeginCopyTo(dst, srcRect, Point.Empty, asyncConfig: cfg); - asyncResult.EndCopyTo(); - } - else - { - IAsyncResult asyncResult = src.BeginDrawInto(dst, srcRect, dstRect, asyncConfig: cfg); - asyncResult.EndDrawInto(); - } + asyncResult.EndDrawInto(); } } - catch (Exception e) + catch (Exception) { // Despite all of the preconditions the memory could not be allocated or some other error occurred (yes, we catch even OutOfMemoryException here) // NOTE: practically we always can recover from here: we simply don't use a generated preview and the worker thread can be finished task.IsCanceled = true; enabled = false; - if (e.IsCriticalGdi()) - throw; } finally { From f024df6c7ef9c6552390ef6185f18931242dc595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 16 Jun 2021 17:00:28 +0200 Subject: [PATCH 122/211] Exception handling: not filtering out critical exceptions in worker threads --- .../ViewModel/CountColorsViewModel.cs | 2 +- .../ViewModel/DownloadResourcesViewModel.cs | 4 ++-- .../ViewModel/TransformBitmapViewModelBase.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs index 9cd192c..5f2286c 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/CountColorsViewModel.cs @@ -155,7 +155,7 @@ private void DoCountColors(object? state) // Waiting to be finished or canceled. As we are on a different thread blocking wait is alright colorCount = asyncResult.EndGetColorCount(); } - catch (Exception e) when (!e.IsCriticalGdi()) + catch (Exception e) { error = e; } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index 5e9a43f..a2495a8 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -206,7 +206,7 @@ private void DoDownloadManifest(object state) IsProcessing = false; }); } - catch (Exception e) when (!e.IsCritical()) + catch (Exception e) { TryInvokeSync(() => { @@ -277,7 +277,7 @@ private void DoDownloadResources(object state) CloseViewCallback?.Invoke(); }); } - catch (Exception e) when (!e.IsCritical()) + catch (Exception e) { // not clearing the downloadedCultures because those files are removed TryInvokeSync(() => diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs index 2c99507..077bc7a 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs @@ -286,7 +286,7 @@ private void DoGenerate(object state) { task.Initialize(originalImage, PreviewImageViewModel.PreviewImage == originalImage); } - catch (Exception e) when (!e.IsCriticalGdi()) + catch (Exception e) { task.SetCompleted(); TryInvokeSync(() => @@ -315,7 +315,7 @@ private void DoGenerate(object state) // Waiting to be finished or canceled. As we are on a different thread blocking wait is alright result = task.EndGenerate(asyncResult); } - catch (Exception e) when (!e.IsCriticalGdi()) + catch (Exception e) { error = e; } From d66a02fec6c8179a3e82767de04cc23dacc58c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 16 Jun 2021 17:05:01 +0200 Subject: [PATCH 123/211] Resetting cursor when clearing the image --- .../KGySoft.Drawing.ImagingTools.csproj | 2 +- KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj index de344ad..c2caeb4 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj @@ -2,7 +2,7 @@ - net35 + net45 false KGySoft.Drawing.ImagingTools bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 7104ee1..aea5115 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -412,6 +412,7 @@ private void AdjustSizes() { sbHorizontal.Visible = sbVertical.Visible = sbHorizontalVisible = sbVerticalVisible = false; targetRectangle = Rectangle.Empty; + Cursor = null; return; } From 2e642cdcef5b36f68d980bd089b65e00505c399a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 16 Jun 2021 19:18:04 +0200 Subject: [PATCH 124/211] Auto applying downloaded resource if it was the selected one --- .../ViewModel/DownloadResourcesViewModel.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index a2495a8..84f078f 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -29,6 +30,7 @@ using KGySoft.ComponentModel; using KGySoft.CoreLibraries; using KGySoft.Drawing.ImagingTools.Model; +using KGySoft.Resources; using KGySoft.Serialization.Xml; #endregion @@ -270,6 +272,8 @@ private void DoDownloadResources(object state) TryInvokeSync(() => { IsProcessing = false; + if (downloadedCultures.Count > 0) + ApplyResources(); if (downloadedCultures.All(i => ResHelper.TryGetCulture(i.CultureName, out var _))) ShowInfo(Res.InfoMessageDownloadCompleted(downloaded)); else @@ -294,6 +298,18 @@ private void DoDownloadResources(object state) } } + private void ApplyResources() + { + ResHelper.ReleaseAllResources(); + CultureInfo current = LanguageSettings.DisplayLanguage; + if (downloadedCultures.All(c => c.CultureName != current.Name)) + return; + + // The current language is among the downloaded ones: applying it + LanguageSettings.DynamicResourceManagersSource = ResourceManagerSources.CompiledAndResX; + ResHelper.RaiseLanguageChanged(); + } + /// /// Returns the downloaded content, or null if task was canceled. /// Always increments 2 in progress: 1 for response, 1 for the downloaded content. From 0cd6c7b5602da7d13c6ffee2af9d89be4055271b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 16 Jun 2021 19:22:55 +0200 Subject: [PATCH 125/211] Language settings: if a language of non-existing resource is set by configuration, auto-generating its resources --- .../ViewModel/LanguageSettingsViewModel.cs | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index 5ddd66a..b3029c1 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -19,9 +19,8 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Globalization; -using System.Linq; + using KGySoft.ComponentModel; using KGySoft.Drawing.ImagingTools.Model; using KGySoft.Resources; @@ -37,7 +36,6 @@ internal class LanguageSettingsViewModel : ViewModelBase private List? neutralLanguages; private HashSet? availableResXLanguages; private List? selectableLanguages; - private CultureInfo? dirtyCulture; #endregion @@ -108,6 +106,7 @@ private List SelectableLanguages internal LanguageSettingsViewModel() { + ResHelper.SavePendingResources(); // generates resource file for possibly non-existing language came from configuration CurrentLanguage = LanguageSettings.DisplayLanguage; AllowResXResources = Configuration.AllowResXResources; UseOSLanguage = Configuration.UseOSLanguage; @@ -150,11 +149,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) } } - protected override void ApplyDisplayLanguage() - { - Debug.Assert(Equals(LanguageSettings.DisplayLanguage, CurrentLanguage), "Only the selected language should be applied"); - UpdateApplyCommandState(); - } + protected override void ApplyDisplayLanguage() => UpdateApplyCommandState(); protected override void Dispose(bool disposing) { @@ -200,7 +195,6 @@ private void UpdateApplyCommandState() // or when turning on/off .resx resources for the default language matters because it also has a resource file CultureInfo selected = CurrentLanguage; ApplyCommandState.Enabled = !Equals(selected, LanguageSettings.DisplayLanguage) - || selected.Equals(dirtyCulture) || (Equals(selected, Res.DefaultLanguage) && (AllowResXResources ^ LanguageSettings.DynamicResourceManagersSource != ResourceManagerSources.CompiledOnly) && AvailableLanguages.Contains(Res.DefaultLanguage)); @@ -212,7 +206,6 @@ private void ApplyAndSave() return; // 1. ) Applying the current language - dirtyCulture = null; CultureInfo currentLanguage = CurrentLanguage; LanguageSettings.DynamicResourceManagersSource = AllowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; @@ -239,7 +232,6 @@ private void ApplyAndSave() { ShowError(Res.ErrorMessageFailedToSaveSettings(e.Message)); } - } #endregion @@ -255,25 +247,17 @@ private void OnEditResourcesCommand() { using IViewModel viewModel = ViewModelFactory.CreateEditResources(CurrentLanguage); ShowChildViewCallback?.Invoke(viewModel); - - // If the language was edited, then enabling apply even if it was disabled - dirtyCulture = viewModel.IsModified ? CurrentLanguage : null; availableResXLanguages = null; selectableLanguages = null; - UpdateApplyCommandState(); } private void OnDownloadResourcesCommand() { using IViewModel> viewModel = ViewModelFactory.CreateDownloadResources(); ShowChildViewCallback?.Invoke(viewModel); - - // If the language was overwritten, then enabling apply even if it was disabled - dirtyCulture = viewModel.IsModified && viewModel.GetEditedModel().Any(i => i.CultureName == CurrentLanguage.Name) ? CurrentLanguage : null; availableResXLanguages = null; selectableLanguages = null; ResetLanguages(); - UpdateApplyCommandState(); } #endregion From 46870d61662fcd9158fa483cf3a60866561dc67f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 16 Jun 2021 19:58:13 +0200 Subject: [PATCH 126/211] DownloadResources: fixing error handling and VM-related resource update on language change --- .../ViewModel/DownloadResourcesViewModel.cs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index 84f078f..2c14b08 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -108,6 +108,7 @@ public DownloadInfo(LocalizationInfo info, LocalizableLibraries library) #region Fields private volatile AsyncTaskBase? activeTask; + private volatile List availableResources = new List(); private volatile HashSet downloadedCultures = new HashSet(); #endregion @@ -197,14 +198,12 @@ private void DoDownloadManifest(object state) using var reader = XmlReader.Create(new StreamReader(new MemoryStream(data), Encoding.UTF8)); reader.ReadStartElement("manifest"); - var itemsList = new List(); + List itemsList = availableResources; XmlSerializer.DeserializeContent(reader, itemsList); TryInvokeSync(() => { - var items = new DownloadableResourceItemCollection(itemsList); - items.ListChanged += Items_ListChanged; - Items = items; + ResetItems(); IsProcessing = false; }); } @@ -223,6 +222,18 @@ private void DoDownloadManifest(object state) } } + private void ResetItems() + { + DownloadableResourceItemCollection? oldItems = Items; + var items = new DownloadableResourceItemCollection(availableResources); + if (oldItems?.IsSorted == true) + items.ApplySort(oldItems.SortProperty!, ((IBindingList)oldItems).SortDirection); + + items.ListChanged += Items_ListChanged; + Items = items; + oldItems?.Dispose(); + } + private void BeginDownloadResources(List toDownload, bool overwrite) { DownloadCommandState.Enabled = false; @@ -287,9 +298,11 @@ private void DoDownloadResources(object state) TryInvokeSync(() => { IsProcessing = false; + if (downloadedCultures.Count > 0) + ApplyResources(); DownloadCommandState.Enabled = true; + ShowError(Res.ErrorMessageFailedToDownloadResource(current, e.Message)); }); - ShowError(Res.ErrorMessageFailedToDownloadResource(current, e.Message)); } finally { @@ -310,6 +323,8 @@ private void ApplyResources() ResHelper.RaiseLanguageChanged(); } + protected override void ApplyDisplayLanguage() => ResetItems(); + /// /// Returns the downloaded content, or null if task was canceled. /// Always increments 2 in progress: 1 for response, 1 for the downloaded content. From c261f91121f56f6b1e64d784e80a9b8c24aa0cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 17 Jun 2021 12:35:52 +0200 Subject: [PATCH 127/211] Fixing possible errors on Mono --- .../View/DebuggerTestForm.Designer.cs | 9 +-- .../View/DebuggerTestForm.cs | 17 ++-- .../ViewModel/DebuggerTestFormViewModel.cs | 78 +++++++++++++------ .../_Classes/OSUtils.cs | 2 + 4 files changed, 73 insertions(+), 33 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.Designer.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.Designer.cs index 33f4853..1da16a7 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.Designer.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.Designer.cs @@ -49,7 +49,7 @@ private void InitializeComponent() // this.btnViewDirect.Dock = System.Windows.Forms.DockStyle.Top; this.btnViewDirect.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.btnViewDirect.Location = new System.Drawing.Point(0, 348); + this.btnViewDirect.Location = new System.Drawing.Point(0, 347); this.btnViewDirect.Name = "btnViewDirect"; this.btnViewDirect.Size = new System.Drawing.Size(196, 24); this.btnViewDirect.TabIndex = 14; @@ -60,7 +60,7 @@ private void InitializeComponent() // this.btnViewByDebugger.Dock = System.Windows.Forms.DockStyle.Top; this.btnViewByDebugger.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.btnViewByDebugger.Location = new System.Drawing.Point(0, 372); + this.btnViewByDebugger.Location = new System.Drawing.Point(0, 371); this.btnViewByDebugger.Name = "btnViewByDebugger"; this.btnViewByDebugger.Size = new System.Drawing.Size(196, 24); this.btnViewByDebugger.TabIndex = 15; @@ -180,7 +180,7 @@ private void InitializeComponent() this.chbAsReadOnly.AutoSize = true; this.chbAsReadOnly.Dock = System.Windows.Forms.DockStyle.Top; this.chbAsReadOnly.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.chbAsReadOnly.Location = new System.Drawing.Point(0, 330); + this.chbAsReadOnly.Location = new System.Drawing.Point(0, 329); this.chbAsReadOnly.Name = "chbAsReadOnly"; this.chbAsReadOnly.Size = new System.Drawing.Size(196, 18); this.chbAsReadOnly.TabIndex = 13; @@ -189,7 +189,6 @@ private void InitializeComponent() // // gbFile // - this.gbFile.AutoSize = true; this.gbFile.Controls.Add(this.rbAsIcon); this.gbFile.Controls.Add(this.rbAsMetafile); this.gbFile.Controls.Add(this.rbAsBitmap); @@ -199,7 +198,7 @@ private void InitializeComponent() this.gbFile.Enabled = false; this.gbFile.Location = new System.Drawing.Point(0, 219); this.gbFile.Name = "gbFile"; - this.gbFile.Size = new System.Drawing.Size(196, 111); + this.gbFile.Size = new System.Drawing.Size(196, 110); this.gbFile.TabIndex = 12; this.gbFile.TabStop = false; this.gbFile.Text = "File Details"; diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.cs index 7718017..54b1f94 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/View/DebuggerTestForm.cs @@ -32,8 +32,8 @@ public partial class DebuggerTestForm : Form private readonly CommandBindingsCollection commandBindings = new CommandBindingsCollection(); private readonly DebuggerTestFormViewModel viewModel = new DebuggerTestFormViewModel(); - private readonly Timer timer; - + private readonly Timer? timer; + private string? errorMessage; #endregion @@ -43,6 +43,7 @@ public partial class DebuggerTestForm : Form public DebuggerTestForm() { InitializeComponent(); + gbFile.AutoSize = !OSUtils.IsMono; cmbPixelFormat.DataSource = viewModel.PixelFormats; commandBindings.AddPropertyBinding(chbAsImage, nameof(CheckBox.Checked), nameof(viewModel.AsImage), viewModel); @@ -84,7 +85,13 @@ public DebuggerTestForm() viewModel.GetHwndCallback = () => Handle; viewModel.GetClipCallback = () => pictureBox.Bounds; - // Due to some strange issue on Linux the app crashes if we show a MessageBox while changing radio buttons + if (OSUtils.IsWindows) + { + viewModel.ErrorCallback = msg => MessageBox.Show(this, msg, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + // Due to some strange issue on Linux the app may crash if we show a MessageBox while changing radio buttons // so as a workaround we show error messages by using a timer. Another solution would be to show a custom dialog. timer = new Timer { Interval = 1 }; viewModel.ErrorCallback = message => @@ -113,7 +120,7 @@ protected override void Dispose(bool disposing) components?.Dispose(); commandBindings.Dispose(); viewModel.Dispose(); - timer.Dispose(); + timer?.Dispose(); } base.Dispose(disposing); @@ -137,7 +144,7 @@ private void OnSelectFileCommand(ICommandSource source) private void OnShowErrorCommand() { - timer.Enabled = false; + timer!.Enabled = false; if (errorMessage != null) MessageBox.Show(this, errorMessage, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); errorMessage = null; diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/DebuggerTestFormViewModel.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/DebuggerTestFormViewModel.cs index 585d88b..3980a64 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/DebuggerTestFormViewModel.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/DebuggerTestFormViewModel.cs @@ -135,25 +135,31 @@ private static Metafile GenerateMetafile() //Set up reference Graphic Graphics refGraph = Graphics.FromHwnd(IntPtr.Zero); IntPtr hdc = refGraph.GetHdc(); - var result = new Metafile(hdc, new Rectangle(0, 0, 100, 100), MetafileFrameUnit.Pixel, EmfType.EmfOnly, "Test"); + try + { + var result = new Metafile(hdc, new Rectangle(0, 0, 100, 100), MetafileFrameUnit.Pixel, EmfType.EmfOnly, "Test"); + + //Draw some silly drawing + using (var g = Graphics.FromImage(result)) + { + var r = new Rectangle(0, 0, 100, 100); + var leftEye = new Rectangle(20, 20, 20, 30); + var rightEye = new Rectangle(60, 20, 20, 30); + g.FillEllipse(Brushes.Yellow, r); + g.FillEllipse(Brushes.White, leftEye); + g.FillEllipse(Brushes.White, rightEye); + g.DrawEllipse(Pens.Black, leftEye); + g.DrawEllipse(Pens.Black, rightEye); + g.DrawBezier(Pens.Red, new Point(10, 50), new Point(10, 100), new Point(90, 100), new Point(90, 50)); + } - //Draw some silly drawing - using (var g = Graphics.FromImage(result)) + return result; + } + finally { - var r = new Rectangle(0, 0, 100, 100); - var leftEye = new Rectangle(20, 20, 20, 30); - var rightEye = new Rectangle(60, 20, 20, 30); - g.FillEllipse(Brushes.Yellow, r); - g.FillEllipse(Brushes.White, leftEye); - g.FillEllipse(Brushes.White, rightEye); - g.DrawEllipse(Pens.Black, leftEye); - g.DrawEllipse(Pens.Black, rightEye); - g.DrawBezier(Pens.Red, new Point(10, 50), new Point(10, 100), new Point(90, 100), new Point(90, 50)); + refGraph.ReleaseHdc(hdc); + refGraph.Dispose(); } - - refGraph.ReleaseHdc(hdc); //cleanup - refGraph.Dispose(); - return result; } #endregion @@ -189,6 +195,10 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) if (e.PropertyName == nameof(TestObject)) { + Image? preview = PreviewImage; + PreviewImage = null; + (preview as IDisposable)?.Dispose(); + object? obj = TestObject; PreviewImage = GetPreviewImage(obj); CanDebug = obj != null; @@ -223,24 +233,39 @@ private void AdjustRadioGroup(string propertyName, IEnumerable group) try { - // actually the transient steps should be disposed, too... as this is just a test, now we rely on the destructor if (Bitmap) - return Icons.Shield.ExtractBitmap(0)!.ConvertPixelFormat(PixelFormat); + { + using Icon icon = Icons.Shield; + using Bitmap bmp = icon.ExtractBitmap(0)!; + return bmp.ConvertPixelFormat(PixelFormat); + } + if (Metafile) return GenerateMetafile(); if (HIcon) return AsImage ? SystemIcons.Application.ToMultiResBitmap() : SystemIcons.Application; + if (ManagedIcon) - return AsImage ? Icons.Application.ToMultiResBitmap() : Icons.Application; + { + if (!AsImage) + return Icons.Application; + using Icon icon = Icons.Application; + return icon.ToMultiResBitmap(); + } + if (GraphicsBitmap) return GetBitmapGraphics(); if (GraphicsHwnd) return GetWindowGraphics(); if (BitmapData) return GetBitmapData(PixelFormat); + if (Palette) - using (var bmp = new Bitmap(1, 1, PixelFormat)) - return bmp.Palette; + { + using var bmp = new Bitmap(1, 1, PixelFormat); + return bmp.Palette; + } + if (SingleColor) return Color.Black; if (ImageFromFile) @@ -326,7 +351,9 @@ static Image ToSupportedFormat(Image image) => { try { - return Graphics.FromImage(Icons.Shield.ExtractBitmap(0)!.ConvertPixelFormat(PixelFormat)); + using Icon icon = Icons.Shield; + using Bitmap bmp = icon.ExtractBitmap(0)!; + return Graphics.FromImage(bmp.ConvertPixelFormat(PixelFormat)); } catch (Exception e) when (e is not StackOverflowException) { @@ -356,7 +383,12 @@ private BitmapData GetBitmapData(PixelFormat pixelFormat) private void FreeTestObject() { - switch (TestObject) + object? obj = TestObject; + if (obj == null) + return; + + TestObject = null; + switch (obj) { case IDisposable disposable: disposable.Dispose(); diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/_Classes/OSUtils.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/_Classes/OSUtils.cs index d46fd64..d67fd8a 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/_Classes/OSUtils.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/_Classes/OSUtils.cs @@ -29,12 +29,14 @@ internal static class OSUtils #region Fields private static bool? isWindows; + private static bool? isMono; #endregion #region Properties internal static bool IsWindows => isWindows ??= Environment.OSVersion.Platform.In(PlatformID.Win32NT, PlatformID.Win32Windows); + internal static bool IsMono => isMono ??= Type.GetType("Mono.Runtime") != null; #endregion } From 71835eaf357a8e3f47fefe3d40ce016f150e1e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 17 Jun 2021 15:52:32 +0200 Subject: [PATCH 128/211] Fixing Mono issues --- .../AdvancedToolStripSplitButton.cs | 6 ++-- .../View/Components/ZoomSplitButton.cs | 4 +-- .../View/Controls/AdvancedToolStrip.cs | 10 +++---- .../View/Controls/BaseControl.cs | 19 +++++++++++++ .../View/Controls/CheckGroupBox.cs | 7 ++--- .../View/Controls/ImageViewer.cs | 18 ++++++------ .../View/Controls/PalettePanel.cs | 4 +-- .../View/Forms/BaseForm.cs | 24 ++++++++++++---- .../View/Forms/ColorSpaceForm.cs | 7 ++++- .../View/Forms/DownloadResourcesForm.cs | 6 ++-- .../View/Forms/ImageVisualizerForm.cs | 4 +-- .../View/Forms/MvvmBaseForm.Designer.cs | 2 -- .../View/Forms/MvvmBaseForm.cs | 2 ++ .../View/Forms/ResizeBitmapForm.cs | 26 ++++++++--------- .../View/_Extensions/ControlExtensions.cs | 28 +++++++++++++------ .../ViewModel/ColorSpaceViewModel.cs | 13 +++++---- .../ViewModel/DownloadResourcesViewModel.cs | 8 +++++- .../ViewModel/TransformBitmapViewModelBase.cs | 2 +- .../WinApi/Constants.cs | 4 ++- .../_Classes/OSUtils.cs | 4 +++ 20 files changed, 130 insertions(+), 68 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs index 310b2e8..fbaab38 100644 --- a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs @@ -116,10 +116,10 @@ protected override void OnButtonClick(EventArgs e) { if (CheckOnClick) Checked = !Checked; - if (OSUtils.IsWindows) - base.OnButtonClick(e); - else + if (OSUtils.IsMono) DefaultItem?.PerformClick(); + else + base.OnButtonClick(e); } protected virtual void OnCheckedChanged(EventArgs e) => (Events[nameof(CheckedChanged)] as EventHandler)?.Invoke(this, e); diff --git a/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs index d074f9e..3198e0e 100644 --- a/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/ZoomSplitButton.cs @@ -96,8 +96,8 @@ protected override void OnParentChanged(ToolStrip? oldParent, ToolStrip? newPare { base.OnParentChanged(oldParent, newParent); - // Linux/Mono: without this the new parent's renderer will not be applied to the drop down menu strip - if (!OSUtils.IsWindows && newParent != null) + // Mono: without this the new parent's renderer will not be applied to the drop down menu strip + if (OSUtils.IsMono && newParent != null) AutoZoomMenuItem.Owner.Renderer = newParent.Renderer; } diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 5afb1de..41a96f2 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -152,7 +152,7 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr // overriding background to behave the same way as ToolStripButton Rectangle rect = btn.ButtonBounds; - if (!OSUtils.IsWindows) + if (OSUtils.IsMono) rect.Location = Point.Empty; else if (e.Item.RightToLeft == RightToLeft.Yes) rect.Offset(-1, 0); @@ -179,8 +179,8 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) { - // Fixing image scaling in menu items on Linux/Mono - if (!OSUtils.IsWindows && e.Item is ToolStripMenuItem mi) + // Fixing image scaling in menu items on Mono + if (OSUtils.IsMono && e.Item is ToolStripMenuItem mi) { Rectangle rect = e.ImageRectangle; rect.Size = e.Item.Owner.ScaleSize(referenceSize); @@ -276,8 +276,8 @@ protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); - // Preventing double scaling in Linux/Mono - if (!OSUtils.IsWindows && Dock.In(DockStyle.Top, DockStyle.Bottom)) + // Preventing double scaling in Mono + if (OSUtils.IsMono && Dock.In(DockStyle.Top, DockStyle.Bottom)) Height = this.ScaleHeight(25); } diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/BaseControl.cs b/KGySoft.Drawing.ImagingTools/View/Controls/BaseControl.cs index 87bb8ac..56cae7a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/BaseControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/BaseControl.cs @@ -27,6 +27,12 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls { internal class BaseControl : Control { + #region Fields + + protected static readonly int MouseWheelScrollDelta = OSUtils.IsMono && OSUtils.IsWindows ? 120 : SystemInformation.MouseWheelScrollDelta; + + #endregion + #region Events internal event EventHandler MouseHWheel @@ -43,6 +49,19 @@ protected override void WndProc(ref Message m) { switch (m.Msg) { + case Constants.WM_PAINT: + try + { + base.WndProc(ref m); + } + catch (Exception e) when (!e.IsCritical()) + { + // In Mono sometimes an internal GDI+ exception happens here + Invalidate(); + } + + break; + // Horizontal scroll case Constants.WM_MOUSEHWHEEL: HandledMouseEventArgs args = new HandledMouseEventArgs(MouseButtons.None, 0, diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs index 2649cde..e9d1370 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs @@ -86,11 +86,10 @@ public CheckGroupBox() // Vista or later: using System FlayStyle so animation is enabled with theming and text is not misplaced with classic themes bool visualStylesEnabled = Application.RenderWithVisualStyles; - checkBox.FlatStyle = OSUtils.IsVistaOrLater ? FlatStyle.System + checkBox.FlatStyle = OSUtils.IsMono ? FlatStyle.Standard + : OSUtils.IsVistaOrLater ? FlatStyle.System // Windows XP: Using standard style with themes so CheckBox color can be set correctly, and using System with classic theme for good placement - : OSUtils.IsWindows ? visualStylesEnabled ? FlatStyle.Standard : FlatStyle.System - // Non-windows (eg. Mono/Linux): Standard for best placement - : FlatStyle.Standard; + : visualStylesEnabled ? FlatStyle.Standard : FlatStyle.System; // GroupBox.FlayStyle must be the same as CheckBox; otherwise, System appearance would be transparent FlatStyle = checkBox.FlatStyle; diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index aea5115..1c7346a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -260,16 +260,16 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData) switch (keyData) { case Keys.Up: - VerticalScroll(SystemInformation.MouseWheelScrollDelta); + VerticalScroll(MouseWheelScrollDelta); return true; case Keys.Down: - VerticalScroll(-SystemInformation.MouseWheelScrollDelta); + VerticalScroll(-MouseWheelScrollDelta); return true; case Keys.Left: - HorizontalScroll(SystemInformation.MouseWheelScrollDelta); + HorizontalScroll(MouseWheelScrollDelta); return true; case Keys.Right: - HorizontalScroll(-SystemInformation.MouseWheelScrollDelta); + HorizontalScroll(-MouseWheelScrollDelta); return true; default: return base.ProcessCmdKey(ref msg, keyData); @@ -318,7 +318,7 @@ protected override void OnMouseWheel(MouseEventArgs e) case Keys.Control: if (autoZoom) SetAutoZoom(false, false); - float delta = (float)e.Delta / SystemInformation.MouseWheelScrollDelta / 5; + float delta = (float)e.Delta / MouseWheelScrollDelta / 5; ApplyZoomChange(delta); break; @@ -378,8 +378,8 @@ private void VerticalScroll(int delta) // When scrolling by mouse, delta is always +-120 so this will be a small change on the scrollbar. // But we collect the fractional changes caused by the touchpad scrolling so it will not be lost either. int totalDelta = scrollFractionVertical + delta * sbVertical.SmallChange; - scrollFractionVertical = totalDelta % SystemInformation.MouseWheelScrollDelta; - int newValue = sbVertical.Value - totalDelta / SystemInformation.MouseWheelScrollDelta; + scrollFractionVertical = totalDelta % MouseWheelScrollDelta; + int newValue = sbVertical.Value - totalDelta / MouseWheelScrollDelta; sbVertical.SetValueSafe(newValue); } @@ -388,8 +388,8 @@ private void HorizontalScroll(int delta) // When scrolling by mouse, delta is always +-120 so this will be a small change on the scrollbar. // But we collect the fractional changes caused by the touchpad scrolling so it will not be lost either. int totalDelta = scrollFractionHorizontal + delta * sbVertical.SmallChange; - scrollFractionHorizontal = totalDelta % SystemInformation.MouseWheelScrollDelta; - int newValue = sbHorizontal.Value - totalDelta / SystemInformation.MouseWheelScrollDelta; + scrollFractionHorizontal = totalDelta % MouseWheelScrollDelta; + int newValue = sbHorizontal.Value - totalDelta / MouseWheelScrollDelta; sbHorizontal.SetValueSafe(newValue); } diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs index cea9347..6cd5ce7 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs @@ -344,8 +344,8 @@ protected override void OnMouseWheel(MouseEventArgs e) // When scrolling by mouse, delta is always +-120 so this will be 1 change on the scrollbar. // But we collect the fractional changes caused by the touchpad scrolling so it will not be lost either. int totalDelta = scrollFraction + e.Delta; - scrollFraction = totalDelta % SystemInformation.MouseWheelScrollDelta; - int newValue = sbPalette.Value - totalDelta / SystemInformation.MouseWheelScrollDelta; + scrollFraction = totalDelta % MouseWheelScrollDelta; + int newValue = sbPalette.Value - totalDelta / MouseWheelScrollDelta; if (newValue < 0) newValue = 0; else if (newValue > sbPalette.Maximum - sbPalette.LargeChange + 1) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs index a30206e..e1c0ee4 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/BaseForm.cs @@ -20,6 +20,7 @@ #if !NET5_0_OR_GREATER using System.Security; using System.Collections.Specialized; +using System.Diagnostics; using System.Drawing; using System.Reflection; @@ -50,9 +51,8 @@ internal class BaseForm : Form #region Fields #if !NET5_0_OR_GREATER - private static readonly BitVector32.Section formStateRenderSizeGrip = OSUtils.IsWindows - ? (BitVector32.Section)Reflector.GetField(typeof(Form), "FormStateRenderSizeGrip")! - : default; + private static BitVector32.Section formStateRenderSizeGrip; + private static BitVector32 formStateFallback = default; private static FieldAccessor? formStateField; #endif @@ -61,8 +61,20 @@ internal class BaseForm : Form #region Properties #if !NET5_0_OR_GREATER - private BitVector32 FormState => (BitVector32)(formStateField ??= - FieldAccessor.GetAccessor(typeof(Form).GetField("formState", BindingFlags.Instance | BindingFlags.NonPublic)!)).Get(this)!; + private BitVector32 FormState + { + get + { + Debug.Assert(OSUtils.IsWindows && !OSUtils.IsMono); + if (formStateField == null) + { + formStateRenderSizeGrip = Reflector.TryGetField(typeof(Form), "FormStateRenderSizeGrip", out object? value) && value is BitVector32.Section section ? section : default; + formStateField = FieldAccessor.GetAccessor(typeof(Form).GetField("formState", BindingFlags.Instance | BindingFlags.NonPublic) ?? typeof(BaseForm).GetField(nameof(formStateFallback), BindingFlags.NonPublic | BindingFlags.Static)!); + } + + return (BitVector32)formStateField.Get(this)!; + } + } #endif #endregion @@ -92,7 +104,7 @@ static BaseForm() #if !NET5_0_OR_GREATER protected override void WndProc(ref Message m) { - if (!OSUtils.IsWindows) + if (!OSUtils.IsWindows || OSUtils.IsMono) { base.WndProc(ref m); return; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.cs index bb06b62..c6b866d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.cs @@ -18,7 +18,7 @@ using System.Linq; using System.Windows.Forms; - +using KGySoft.Drawing.ImagingTools.View.Controls; using KGySoft.Drawing.ImagingTools.ViewModel; #endregion @@ -43,6 +43,11 @@ internal ColorSpaceForm(ColorSpaceViewModel viewModel) { InitializeComponent(); + // Mono/Windows: exiting because ToolTips throw an exception if set for an embedded control and + // since they don't appear for negative padding there is simply no place for them. + if (OSUtils.IsMono && OSUtils.IsWindows) + return; + ValidationMapping[nameof(viewModel.PixelFormat)] = gbPixelFormat.CheckBox; ValidationMapping[nameof(viewModel.QuantizerSelectorViewModel.Quantizer)] = gbQuantizer.CheckBox; ValidationMapping[nameof(viewModel.DithererSelectorViewModel.Ditherer)] = gbDitherer.CheckBox; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs index 3373798..176811b 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.cs @@ -93,8 +93,8 @@ private void InitCommandBindings() .AddSource(okCancelButtons.OKButton, nameof(okCancelButtons.OKButton.Click)); CommandBindings.Add(ViewModel.CancelCommand) .AddSource(okCancelButtons.CancelButton, nameof(okCancelButtons.CancelButton.Click)); - CommandBindings.Add(OnDirtyStateChangedCommand) - .AddSource(gridDownloadableResources, nameof(gridDownloadableResources.CurrentCellDirtyStateChanged)); + CommandBindings.Add(OnCellContentClickCommand) + .AddSource(gridDownloadableResources, nameof(gridDownloadableResources.CellContentClick)); } private void InitPropertyBindings() @@ -113,7 +113,7 @@ private void InitPropertyBindings() #region Command Handlers - private void OnDirtyStateChangedCommand() + private void OnCellContentClickCommand() { if (gridDownloadableResources.CurrentCell is DataGridViewCheckBoxCell { EditingCellValueChanged: true }) gridDownloadableResources.CommitEdit(DataGridViewDataErrorContexts.Commit); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index 1d75d68..3c79b10 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -83,8 +83,8 @@ private static Image GetCompoundViewIcon(ImageInfoType type) protected override void OnLoad(EventArgs e) { - tsMenu.FixAppearance(); base.OnLoad(e); + tsMenu.FixAppearance(); } protected override void ApplyResources() @@ -121,8 +121,8 @@ protected override void ApplyResources() protected override void ApplyStringResources() { base.ApplyStringResources(); - // base cannot handle these because components do not have names and dialogs are not even added to components field + // base cannot handle these because components do not have names and dialogs are not even added to components field dlgOpen.Title = Res.Get($"{nameof(dlgOpen)}.{nameof(dlgOpen.Title)}"); dlgSave.Title = Res.Get($"{nameof(dlgSave)}.{nameof(dlgSave.Title)}"); } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs index b5accdc..6ef7bce 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.Designer.cs @@ -22,9 +22,7 @@ private void InitializeComponent() this.ClientSize = new System.Drawing.Size(284, 261); this.Name = "MvvmBaseForm"; this.RightToLeftLayout = true; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.ResumeLayout(false); - } } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index 48378a4..49557c7 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -85,6 +85,8 @@ protected MvvmBaseForm(TViewModel viewModel) handleCreated = new ManualResetEventSlim(); ApplyRightToLeft(); InitializeComponent(); + StartPosition = OSUtils.IsMono && OSUtils.IsWindows ? FormStartPosition.WindowsDefaultLocation : FormStartPosition.CenterParent; + // occurs in design mode but DesignMode is false for grandchild forms if (viewModel == null!) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs index 5d152e4..e55386f 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.cs @@ -120,31 +120,31 @@ private void InitPropertyBindings() CommandBindings.AddPropertyBinding(ViewModel, nameof(VM.ByPixels), nameof(Enabled), txtWidthPx, txtHeightPx); // Regular WinForms binding behaves a bit better because it does not clear the currently edited text box on parse error - // but it fails to sync the other properties properly on Linux/Mono so using KGy SOFT binding on non-Windows systems. + // but it fails to sync the other properties properly on Mono so using KGy SOFT binding in Mono systems. // VM.WidthRatio <-> txtWidthPercent.Text - if (OSUtils.IsWindows) - AddWinFormsBinding(nameof(VM.WidthRatio), txtWidthPercent, nameof(txtWidthPercent.Text), FormatPercentage, ParsePercentage); - else + if (OSUtils.IsMono) CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.WidthRatio), txtWidthPercent, nameof(txtWidthPercent.Text), FormatPercentage!, ParsePercentage!); + else + AddWinFormsBinding(nameof(VM.WidthRatio), txtWidthPercent, nameof(txtWidthPercent.Text), FormatPercentage, ParsePercentage); // VM.HeightRatio <-> txtHeightPercent.Text - if (OSUtils.IsWindows) - AddWinFormsBinding(nameof(VM.HeightRatio), txtHeightPercent, nameof(txtHeightPercent.Text), FormatPercentage, ParsePercentage); - else + if (OSUtils.IsMono) CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.HeightRatio), txtHeightPercent, nameof(txtHeightPercent.Text), FormatPercentage!, ParsePercentage!); + else + AddWinFormsBinding(nameof(VM.HeightRatio), txtHeightPercent, nameof(txtHeightPercent.Text), FormatPercentage, ParsePercentage); // VM.Width <-> txtWidthPx.Text - if (OSUtils.IsWindows) - AddWinFormsBinding(nameof(VM.Width), txtWidthPx, nameof(txtWidthPx.Text), FormatInteger, ParseInteger); - else + if (OSUtils.IsMono) CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.Width), txtWidthPx, nameof(txtWidthPx.Text), FormatInteger!, ParseInteger!); + else + AddWinFormsBinding(nameof(VM.Width), txtWidthPx, nameof(txtWidthPx.Text), FormatInteger, ParseInteger); // VM.Height <-> txtHeightPx.Text - if (OSUtils.IsWindows) - AddWinFormsBinding(nameof(VM.Height), txtHeightPx, nameof(txtHeightPx.Text), FormatInteger, ParseInteger); - else + if (OSUtils.IsMono) CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(VM.Height), txtHeightPx, nameof(txtHeightPx.Text), FormatInteger!, ParseInteger!); + else + AddWinFormsBinding(nameof(VM.Height), txtHeightPx, nameof(txtHeightPx.Text), FormatInteger, ParseInteger); } private void AddWinFormsBinding(string sourceName, IBindableComponent target, string propertyName, Func format, Func parse) diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs index 000e99c..90af8ea 100644 --- a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs @@ -127,13 +127,23 @@ static void ApplyToolStripResources(ToolStripItemCollection items) internal static void FixAppearance(this ToolStrip toolStrip) { - static void FixItems(ToolStripItemCollection items, Color replacementColor) + static void FixItems(ToolStripItemCollection items, Color? replacementColor) { foreach (ToolStripItem item in items) { - // to self - if ((item is ToolStripMenuItem || item is ToolStripLabel || item is ToolStripSeparator || item is ToolStripProgressBar) && item.BackColor.ToArgb() == replacementColor.ToArgb()) - item.BackColor = replacementColor; + // fixing closing menu due to the appearing tool tip (only on Mono/Windows) + if (OSUtils.IsWindows && item is ToolStripDropDownButton or ToolStripSplitButton) + { + item.AutoToolTip = false; + item.ToolTipText = null; + } + + // fixing menu color + if (replacementColor.HasValue) + { + if ((item is ToolStripMenuItem || item is ToolStripLabel || item is ToolStripSeparator || item is ToolStripProgressBar) && item.BackColor.ToArgb() == replacementColor.Value.ToArgb()) + item.BackColor = replacementColor.Value; + } // to children if (item is ToolStripDropDownItem dropDownItem) @@ -141,12 +151,14 @@ static void FixItems(ToolStripItemCollection items, Color replacementColor) } } - if (OSUtils.IsWindows || SystemInformation.HighContrast) + if (!OSUtils.IsMono) return; - // fixing "dark on dark" menu issue on Linux - Color replacementColor = Color.FromArgb(ProfessionalColors.MenuStripGradientBegin.ToArgb()); - toolStrip.BackColor = replacementColor; + // fixing "dark on dark" menu issue on Mono/Linux + Color? replacementColor = OSUtils.IsLinux && !SystemInformation.HighContrast ? Color.FromArgb(ProfessionalColors.MenuStripGradientBegin.ToArgb()) : null; + if (replacementColor.HasValue) + toolStrip.BackColor = replacementColor.Value; + FixItems(toolStrip.Items, replacementColor); } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ColorSpaceViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ColorSpaceViewModel.cs index 9419cd1..ff40bda 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ColorSpaceViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ColorSpaceViewModel.cs @@ -93,13 +93,16 @@ internal override IAsyncResult BeginGenerate(AsyncConfig asyncConfig) internal override void SetCompleted() { - if (isSourceCloned) + if (sourceBitmap != null) { - sourceBitmap?.Dispose(); - sourceBitmap = null; + if (isSourceCloned) + { + sourceBitmap.Dispose(); + sourceBitmap = null; + } + else + Monitor.Exit(sourceBitmap); } - else - Monitor.Exit(sourceBitmap!); base.SetCompleted(); } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index 2c14b08..bc43fc1 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -231,6 +231,7 @@ private void ResetItems() items.ListChanged += Items_ListChanged; Items = items; + DownloadCommandState.Enabled = false; oldItems?.Dispose(); } @@ -300,7 +301,7 @@ private void DoDownloadResources(object state) IsProcessing = false; if (downloadedCultures.Count > 0) ApplyResources(); - DownloadCommandState.Enabled = true; + DownloadCommandState.Enabled = Items?.Any(i => i.Selected) == true; ShowError(Res.ErrorMessageFailedToDownloadResource(current, e.Message)); }); } @@ -311,6 +312,11 @@ private void DoDownloadResources(object state) } } + private void ResetDownloadCommandState() + { + throw new NotImplementedException(); + } + private void ApplyResources() { ResHelper.ReleaseAllResources(); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs index 077bc7a..573c322 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs @@ -273,6 +273,7 @@ private void DoGenerate(object state) // original image if (MatchesOriginal(task)) { + task.SetCompleted(); TryInvokeSync(() => { SetPreview(originalImage); @@ -308,7 +309,6 @@ private void DoGenerate(object state) // ReSharper disable once AccessToDisposedClosure - false alarm, newTask.Dispose() is called only on error IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false, - State = task, Progress = drawingProgressManager }); diff --git a/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs b/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs index 79cadad..1ba4f27 100644 --- a/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs +++ b/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs @@ -22,10 +22,12 @@ internal static class Constants // ReSharper disable InconsistentNaming internal const int WS_BORDER = 0x00800000; + + internal const int WM_PAINT = 0x0F; internal const int WM_MOUSEHWHEEL = 0x020E; internal const int WM_MOUSEACTIVATE = 0x021; - internal const int MA_ACTIVATEANDEAT= 2; + internal const int MA_ACTIVATEANDEAT = 2; internal const int MA_ACTIVATE = 1; // ReSharper restore InconsistentNaming diff --git a/KGySoft.Drawing.ImagingTools/_Classes/OSUtils.cs b/KGySoft.Drawing.ImagingTools/_Classes/OSUtils.cs index 8989301..fdc8c29 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/OSUtils.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/OSUtils.cs @@ -31,12 +31,16 @@ internal static class OSUtils private static bool? isVistaOrLater; private static bool? isWin8OrLater; private static bool? isWindows; + private static bool? isLinux; + private static bool? isMono; #endregion #region Properties internal static bool IsWindows => isWindows ??= Environment.OSVersion.Platform.In(PlatformID.Win32NT, PlatformID.Win32Windows); + internal static bool IsLinux => isLinux ??= Environment.OSVersion.Platform.In(PlatformID.Unix, (PlatformID)128); + internal static bool IsMono => isMono ??= Type.GetType("Mono.Runtime") != null; internal static bool IsVistaOrLater { From cd287e0e7ace20db865fb373c42e469a8fdf6544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 18 Jun 2021 21:08:50 +0200 Subject: [PATCH 129/211] Optimizing async behavior --- .../ViewModel/TransformBitmapViewModelBase.cs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs index 573c322..b79faf9 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs @@ -193,7 +193,7 @@ protected virtual ValidationResultsCollection DoValidation() protected void BeginGeneratePreview() { - // sending cancel request to pending generate but the completion is awaited on a pool thread to prevent deadlocks + // sending cancel request to pending generate but the completion is awaited on a pool thread to prevent the UI from lagging CancelRunningGenerate(); GeneratePreviewError = null; @@ -205,8 +205,9 @@ protected void BeginGeneratePreview() return; } + // Not awaiting the canceled task here to prevent the UI from lagging. IsGenerating = true; - ThreadPool.QueueUserWorkItem(DoGenerate!, CreateGenerateTask()); + ThreadPool.QueueUserWorkItem(DoGenerate, CreateGenerateTask()); } protected abstract GenerateTaskBase CreateGenerateTask(); @@ -249,8 +250,21 @@ private void DoGenerate(object state) { var task = (GenerateTaskBase)state; - // this is a fairly large lock ensuring that only one generate task is running at once - lock (syncRoot) + // This is a fairly large lock ensuring that only one generate task is running at once. + // Instead of this sync we could await the canceled task before queuing a new one but then the UI can freeze for some moments. + // (It wouldn't cause deadlocks because here every TryInvokeSync is after completing the task.) + // But many threads can be queued, which all stop here before acquiring the lock. To prevent spawning too many threads we + // don't use a regular lock here but a bit active spinning that can exit without taking the lock if the task gets outdated. + while (!Monitor.TryEnter(syncRoot, 1)) + { + + if (!IsDisposed && MatchesSettings(task)) + continue; + task.Dispose(); + return; + } + + try { // lost race if (IsDisposed || !MatchesSettings(task)) @@ -259,6 +273,7 @@ private void DoGenerate(object state) return; } + // Awaiting the previous unfinished task. This could be also in BeginGeneratePreview but that may freeze the UI for some time. Debug.Assert(activeTask?.IsCanceled != false); WaitForPendingGenerate(); Debug.Assert(activeTask == null); @@ -309,7 +324,7 @@ private void DoGenerate(object state) // ReSharper disable once AccessToDisposedClosure - false alarm, newTask.Dispose() is called only on error IsCancelRequestedCallback = () => task.IsCanceled, ThrowIfCanceled = false, - Progress = drawingProgressManager + Progress = drawingProgressManager, }); // Waiting to be finished or canceled. As we are on a different thread blocking wait is alright @@ -344,6 +359,10 @@ private void DoGenerate(object state) activeTask = null; } } + finally + { + Monitor.Exit(syncRoot); + } } private void TrySetProgress(DrawingProgress progress) From 4736f4c392d0860ee1fcf19fb7f45aeda719743f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 18 Jun 2021 21:09:33 +0200 Subject: [PATCH 130/211] Localization support for dialogs --- KGySoft.Drawing.ImagingTools/View/Dialogs.cs | 64 +++++++- .../WinApi/CWPRETSTRUCT.cs | 40 +++++ .../WinApi/Constants.cs | 8 +- .../WinApi/EnumChildProc.cs | 33 ++++ .../WinApi/HOOKPROC.cs | 36 +++++ .../WinApi/Kernel32.cs | 9 ++ KGySoft.Drawing.ImagingTools/WinApi/User32.cs | 143 +++++++++++++++++- 7 files changed, 326 insertions(+), 7 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/WinApi/CWPRETSTRUCT.cs create mode 100644 KGySoft.Drawing.ImagingTools/WinApi/EnumChildProc.cs create mode 100644 KGySoft.Drawing.ImagingTools/WinApi/HOOKPROC.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs index 5e0818d..e904e37 100644 --- a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs +++ b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs @@ -16,18 +16,45 @@ #region Usings +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; using System.Windows.Forms; +using KGySoft.Drawing.ImagingTools.WinApi; + #endregion namespace KGySoft.Drawing.ImagingTools.View { internal static class Dialogs { + #region Fields + + private static readonly IntPtr windowHook; + + private static readonly HOOKPROC callWndRetProc = CallWndRetProc; + private static readonly EnumChildProc enumChildProc = EnumChildProc; + + #endregion + + #region Constructors + + static Dialogs() + { + if (!OSUtils.IsWindows || OSUtils.IsMono) + return; + windowHook = User32.HookCallWndRetProc(callWndRetProc); + if (windowHook != IntPtr.Zero) + AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; + } + + #endregion + #region Methods #region Internal Methods - + internal static void ErrorMessage(string message) => Show(message, Res.TitleError, MessageBoxButtons.OK, MessageBoxIcon.Error); internal static void InfoMessage(string message) => Show(message, Res.TitleInformation, MessageBoxButtons.OK, MessageBoxIcon.Information); internal static void WarningMessage(string message) => Show(message, Res.TitleWarning, MessageBoxButtons.OK, MessageBoxIcon.Warning); @@ -50,6 +77,41 @@ internal static bool ConfirmMessage(string message, bool isYesDefault = true) private static DialogResult Show(string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1) => MessageBox.Show(message, caption, buttons, icon, defaultButton, LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft ? MessageBoxOptions.RightAlign | MessageBoxOptions.RtlReading : default); + private static IntPtr CallWndRetProc(int nCode, IntPtr wParam, IntPtr lParam) + { + if (nCode >= 0) + { + var msg = (CWPRETSTRUCT)Marshal.PtrToStructure(lParam, typeof(CWPRETSTRUCT)); + if (msg.message == Constants.WM_INITDIALOG) + { + string name = User32.GetClassName(msg.hwnd); + if (name == Constants.ClassNameDialogBox) + User32.EnumChildWindows(msg.hwnd, enumChildProc); + } + } + + return User32.CallNextHook(nCode, wParam, lParam); + } + + private static bool EnumChildProc(IntPtr hWnd, IntPtr lParam) + { + string className = User32.GetClassName(hWnd); + int id = User32.GetDialogControlId(hWnd); + User32.SetWindowText(hWnd, $"{className}.{id}"); + return true; + } + + #endregion + + #region Event Handlers + + private static void CurrentDomain_ProcessExit(object sender, EventArgs e) + { + Debug.Assert(windowHook != IntPtr.Zero); + if (windowHook != IntPtr.Zero) + User32.UnhookWindowsHook(windowHook); + } + #endregion #endregion diff --git a/KGySoft.Drawing.ImagingTools/WinApi/CWPRETSTRUCT.cs b/KGySoft.Drawing.ImagingTools/WinApi/CWPRETSTRUCT.cs new file mode 100644 index 0000000..9689961 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/WinApi/CWPRETSTRUCT.cs @@ -0,0 +1,40 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: CWPRETSTRUCT.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +using System.Runtime.InteropServices; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.WinApi +{ + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal struct CWPRETSTRUCT + { + #region Fields + + internal IntPtr lResult; + internal IntPtr lParam; + internal IntPtr wParam; + internal uint message; + internal IntPtr hwnd; + + #endregion + } +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs b/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs index 1ba4f27..4cf8cae 100644 --- a/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs +++ b/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs @@ -24,12 +24,18 @@ internal static class Constants internal const int WS_BORDER = 0x00800000; internal const int WM_PAINT = 0x0F; - internal const int WM_MOUSEHWHEEL = 0x020E; internal const int WM_MOUSEACTIVATE = 0x021; + internal const int WM_INITDIALOG = 0x0110; + internal const int WM_MOUSEHWHEEL = 0x020E; internal const int MA_ACTIVATEANDEAT = 2; internal const int MA_ACTIVATE = 1; + internal const int WH_CALLWNDPROCRET = 12; + + internal const string ClassNameDialogBox = "#32770"; + internal const string ClassNameButton = "Button"; + // ReSharper restore InconsistentNaming #endregion } diff --git a/KGySoft.Drawing.ImagingTools/WinApi/EnumChildProc.cs b/KGySoft.Drawing.ImagingTools/WinApi/EnumChildProc.cs new file mode 100644 index 0000000..617769c --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/WinApi/EnumChildProc.cs @@ -0,0 +1,33 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: EnumChildProc.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.WinApi +{ + /// + /// An application-defined callback function used with the EnumChildWindows function. It receives the child window handles. + /// The WNDENUMPROC type defines a pointer to this callback function. EnumChildProc is a placeholder for the application-defined function name. + /// + /// A handle to a child window of the parent window specified in EnumChildWindows. + /// The application-defined value given in EnumChildWindows. + /// To continue enumeration, the callback function must return TRUE; to stop enumeration, it must return FALSE. + internal delegate bool EnumChildProc(IntPtr hWnd, IntPtr lParam); +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/WinApi/HOOKPROC.cs b/KGySoft.Drawing.ImagingTools/WinApi/HOOKPROC.cs new file mode 100644 index 0000000..f545df7 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/WinApi/HOOKPROC.cs @@ -0,0 +1,36 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: HOOKPROC.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.WinApi +{ + /// + /// An application-defined or library-defined callback function used with the SetWindowsHookEx function. The system calls this function after the SendMessage function is called. + /// The hook procedure can examine the message; it cannot modify it. + /// The HOOKPROC type defines a pointer to this callback function. CallWndRetProc is a placeholder for the application-defined or library-defined function name. + /// + /// Specifies whether the hook procedure must process the message. If nCode is HC_ACTION, the hook procedure must process the message. + /// If nCode is less than zero, the hook procedure must pass the message to the CallNextHookEx function without further processing and must return the value returned by CallNextHookEx. + /// Specifies whether the message is sent by the current process. If the message is sent by the current process, it is nonzero; otherwise, it is NULL. + /// A pointer to a structure that contains details about the message. + /// + internal delegate IntPtr HOOKPROC(int nCode, IntPtr wParam, IntPtr lParam); +} \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/WinApi/Kernel32.cs b/KGySoft.Drawing.ImagingTools/WinApi/Kernel32.cs index 6630d3b..eb1b547 100644 --- a/KGySoft.Drawing.ImagingTools/WinApi/Kernel32.cs +++ b/KGySoft.Drawing.ImagingTools/WinApi/Kernel32.cs @@ -58,6 +58,13 @@ private static class NativeMethods [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer); + /// + /// Retrieves the thread identifier of the calling thread. + /// + /// The return value is the thread identifier of the calling thread. + [DllImport("kernel32.dll")] + internal static extern uint GetCurrentThreadId(); + #endregion } @@ -80,6 +87,8 @@ internal static long GetTotalMemory() return (long)status.ullTotalPhys; } + internal static uint GetCurrentThreadId() => NativeMethods.GetCurrentThreadId(); + #endregion } } diff --git a/KGySoft.Drawing.ImagingTools/WinApi/User32.cs b/KGySoft.Drawing.ImagingTools/WinApi/User32.cs index ae1a556..42173e4 100644 --- a/KGySoft.Drawing.ImagingTools/WinApi/User32.cs +++ b/KGySoft.Drawing.ImagingTools/WinApi/User32.cs @@ -1,5 +1,4 @@ -#if !NET5_0_OR_GREATER -#region Copyright +#region Copyright /////////////////////////////////////////////////////////////////////////////// // File: User32.cs @@ -18,10 +17,16 @@ #region Usings using System; +using System.ComponentModel; +#if !NET5_0_OR_GREATER using System.Drawing; +#endif using System.Runtime.InteropServices; using System.Security; +using System.Text; +#if !NET5_0_OR_GREATER using System.Windows.Forms; +#endif #endregion @@ -30,29 +35,157 @@ namespace KGySoft.Drawing.ImagingTools.WinApi [SecurityCritical] internal static class User32 { - #region Nested classes - #region NativeMethods class private static class NativeMethods { #region Methods +#if !NET5_0_OR_GREATER + /// + /// The ScreenToClient function converts the screen coordinates of a specified point on the screen to client-area coordinates. + /// + /// A handle to the window whose client area will be used for the conversion. + /// A pointer to a POINT structure that specifies the screen coordinates to be converted. + /// If the function succeeds, the return value is nonzero. + /// If the function fails, the return value is zero. [DllImport("user32.dll")] internal static extern bool ScreenToClient(IntPtr hWnd, ref Point lpPoint); +#endif + + /// + /// Installs an application-defined hook procedure into a hook chain. You would install a hook procedure to monitor the system for certain types of events. + /// These events are associated either with a specific thread or with all threads in the same desktop as the calling thread. + /// + /// The type of hook procedure to be installed. This parameter can be one of the WH_ values. + /// A pointer to the hook procedure. If the dwThreadId parameter is zero or specifies the identifier of a thread created by a different process, + /// the lpfn parameter must point to a hook procedure in a DLL. Otherwise, lpfn can point to a hook procedure in the code associated with the current process. + /// A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. + /// The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within + /// the code associated with the current process. + /// The identifier of the thread with which the hook procedure is to be associated. For desktop apps, if this parameter is zero, + /// the hook procedure is associated with all existing threads running in the same desktop as the calling thread. + /// If the function succeeds, the return value is the handle to the hook procedure. + /// If the function fails, the return value is NULL. To get extended error information, call GetLastError. + [DllImport("user32.dll", SetLastError = true)] + internal static extern IntPtr SetWindowsHookEx(int idHook, HOOKPROC lpfn, IntPtr hInstance, uint threadId); + + /// + /// Removes a hook procedure installed in a hook chain by the function. + /// + /// A handle to the hook to be removed. + /// This parameter is a hook handle obtained by a previous call to . + /// If the function succeeds, the return value is nonzero. + /// If the function fails, the return value is zero.To get extended error information, call GetLastError. + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool UnhookWindowsHookEx(IntPtr idHook); + + /// + /// Passes the hook information to the next hook procedure in the current hook chain. + /// A hook procedure can call this function either before or after processing the hook information. + /// + /// This parameter is ignored. + /// The hook code passed to the current hook procedure. + /// The next hook procedure uses this code to determine how to process the hook information. + /// The wParam value passed to the current hook procedure. + /// The meaning of this parameter depends on the type of hook associated with the current hook chain. + /// The lParam value passed to the current hook procedure. + /// The meaning of this parameter depends on the type of hook associated with the current hook chain. + /// This value is returned by the next hook procedure in the chain. The current hook procedure must also return this value. + /// The meaning of the return value depends on the hook type. For more information, see the descriptions of the individual hook procedures. + [DllImport("user32.dll")] + internal static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); + + /// + /// Retrieves the name of the class to which the specified window belongs. + /// + /// A handle to the window and, indirectly, the class to which the window belongs. + /// The class name string. + /// The length of the lpClassName buffer, in characters. + /// The buffer must be large enough to include the terminating null character; otherwise, the class name string is truncated to nMaxCount-1 characters. + /// If the function succeeds, the return value is the number of characters copied to the buffer, not including the terminating null character. + /// If the function fails, the return value is zero. To get extended error information, call GetLastError function. + [DllImport("user32.dll", SetLastError = true)] + internal static extern int GetClassName(IntPtr hWnd, [Out]char[] lpClassName, int nMaxCount); + + /// + /// Enumerates the child windows that belong to the specified parent window by passing the handle to each child window, in turn, to an application-defined callback function. + /// EnumChildWindows continues until the last child window is enumerated or the callback function returns FALSE. + /// + /// A handle to the parent window whose child windows are to be enumerated. If this parameter is NULL, this function is equivalent to EnumWindows. + /// A pointer to an application-defined callback function. For more information, see EnumChildProc. + /// An application-defined value to be passed to the callback function. + /// The return value is not used. + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool EnumChildWindows(IntPtr hWndParent, EnumChildProc lpEnumFunc, IntPtr lParam); + + /// + /// Retrieves the identifier of the specified control. + /// + /// A handle to the control. + /// If the function succeeds, the return value is the identifier of the control. + /// If the function fails, the return value is zero.An invalid value for the hWnd parameter, for example, will cause the function to fail.To get extended error information, call GetLastError. + [DllImport("user32.dll", SetLastError = true)] + internal static extern int GetDlgCtrlID(IntPtr hWnd); + + /// + /// Changes the text of the specified window's title bar (if it has one). If the specified window is a control, the text of the control is changed. + /// However, SetWindowText cannot change the text of a control in another application. + /// + /// A handle to the window or control whose text is to be changed. + /// The new title or control text. + /// If the function succeeds, the return value is nonzero. + /// If the function fails, the return value is zero.To get extended error information, call GetLastError. + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetWindowText(IntPtr hWnd, string lpString); #endregion } #endregion + #region Fields + + /// + /// Used in , length is enough for the longest possible name with null terminator as described here: https://docs.microsoft.com/en-us/windows/win32/winmsg/about-window-classes + /// + private static readonly char[] classNameBuf = new char[11]; + #endregion #region Methods +#if !NET5_0_OR_GREATER internal static void ScreenToClient(Control control, ref Point point) => NativeMethods.ScreenToClient(control.Handle, ref point); +#endif + + internal static IntPtr HookCallWndRetProc(HOOKPROC hookProc) + => NativeMethods.SetWindowsHookEx(Constants.WH_CALLWNDPROCRET, hookProc, IntPtr.Zero, Kernel32.GetCurrentThreadId()); + + internal static void UnhookWindowsHook(IntPtr hook) => NativeMethods.UnhookWindowsHookEx(hook); + + internal static IntPtr CallNextHook(int code, IntPtr wParam, IntPtr lParam) + => NativeMethods.CallNextHookEx(IntPtr.Zero, code, wParam, lParam); + + internal static string GetClassName(IntPtr handle) + { + if (handle == IntPtr.Zero) + throw new ArgumentNullException(nameof(handle), PublicResources.ArgumentNull); + int length = NativeMethods.GetClassName(handle, classNameBuf, classNameBuf.Length); + if (length == 0) + throw new Win32Exception(Marshal.GetLastWin32Error()); + return new String(classNameBuf, 0, length); + } + + internal static void EnumChildWindows(IntPtr handle, EnumChildProc enumProc) => NativeMethods.EnumChildWindows(handle, enumProc, IntPtr.Zero); + + internal static int GetDialogControlId(IntPtr handle) => NativeMethods.GetDlgCtrlID(handle); + + internal static bool SetWindowText(IntPtr handle, string text) => NativeMethods.SetWindowText(handle, text); #endregion } } -#endif \ No newline at end of file From f4b12ce73eb6c468c49bae77d309c268763ce485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 20 Jun 2021 13:20:26 +0200 Subject: [PATCH 131/211] Localization of standard dialogs --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 74 +++++++- KGySoft.Drawing.ImagingTools/Res.cs | 12 ++ KGySoft.Drawing.ImagingTools/View/Dialogs.cs | 175 ++++++++++++++---- .../View/Forms/ImageVisualizerForm.cs | 6 +- .../View/Forms/ManageInstallationsForm.cs | 7 +- .../ColorVisualizerControl.Designer.cs | 7 - .../UserControls/ColorVisualizerControl.cs | 6 +- .../WinApi/Constants.cs | 4 + KGySoft.Drawing.ImagingTools/WinApi/User32.cs | 3 +- 9 files changed, 232 insertions(+), 62 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index adae042..5ff5544 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -138,6 +138,18 @@ Confirmation + + Open Image + + + Save Image As + + + Select Color + + + Browse For Folder + ; @@ -660,12 +672,6 @@ Copyright © {2} KGy SOFT. All rights reserved. Back Color - - Open Image - - - Save Image As - Default @@ -860,7 +866,7 @@ Newly generated resources will contain English texts prefixed with "[T]". You ca Resource Entries - Filter: + &Filter: Filtering ignores case, accents and character width. @@ -920,4 +926,58 @@ Tip: use '[T]' to filter untranslated texts. About... + + &OK + + + &Cancel + + + &Yes + + + &No + + + &Basic Colors: + + + Custo&m Colors: + + + &Define Custom Colors >> + + + Result + + + color + + + Hu&e: + + + &Sat: + + + &Lum: + + + &Red: + + + &Green: + + + &Blue: + + + &Add to Custom Colors + + + Select the target folder of the installation. + + + &Make New Folder + \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index dac1261..1fe4a55 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -84,6 +84,18 @@ internal static class Res /// Confirmation internal static string TitleConfirmation => Get("Title_Confirmation"); + /// Open Image + internal static string TitleOpenFileDialog => Get("Title_OpenFileDialog"); + + /// Save Image As + internal static string TitleSaveFileDialog => Get("Title_SaveFileDialog"); + + /// Color + internal static string TitleColorDialog => Get("Title_ColorDialog"); + + /// Browse For Folder + internal static string TitleFolderDialog => Get("Title_FolderDialog"); + #endregion #region Texts diff --git a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs index e904e37..4e807b5 100644 --- a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs +++ b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs @@ -17,7 +17,7 @@ #region Usings using System; -using System.Diagnostics; +using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; @@ -29,25 +29,46 @@ namespace KGySoft.Drawing.ImagingTools.View { internal static class Dialogs { - #region Fields + #region Nested Types - private static readonly IntPtr windowHook; + #region DialogType enum + + private enum DialogType + { + MessageBoxSingleButton, + MessageBoxMoreButtons, + ColorDialog, + FolderDialog + } - private static readonly HOOKPROC callWndRetProc = CallWndRetProc; - private static readonly EnumChildProc enumChildProc = EnumChildProc; + #region EnumerationContext struct + + private struct DialogContext + { + #region Fields + + internal DialogType DialogType; + internal int CustomStaticId; + internal bool AllowCustomStaticLocalization; + + #endregion + } #endregion - #region Constructors + #endregion - static Dialogs() - { - if (!OSUtils.IsWindows || OSUtils.IsMono) - return; - windowHook = User32.HookCallWndRetProc(callWndRetProc); - if (windowHook != IntPtr.Zero) - AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; - } + #endregion + + #region Fields + + // These delegates are stored as a field to prevent their possible garbage collection while used by P/Invoke call. + private static readonly HOOKPROC callWndRetProc = CallWndRetProc; + private static readonly EnumChildProc enumChildProc = EnumChildProc; + + private static DialogContext dialogContext; + private static ColorDialog? colorDialog; + private static FolderBrowserDialog? folderDialog; #endregion @@ -55,27 +76,99 @@ static Dialogs() #region Internal Methods - internal static void ErrorMessage(string message) => Show(message, Res.TitleError, MessageBoxButtons.OK, MessageBoxIcon.Error); - internal static void InfoMessage(string message) => Show(message, Res.TitleInformation, MessageBoxButtons.OK, MessageBoxIcon.Information); - internal static void WarningMessage(string message) => Show(message, Res.TitleWarning, MessageBoxButtons.OK, MessageBoxIcon.Warning); + internal static void ErrorMessage(string message) => ShowMessageBox(message, Res.TitleError, MessageBoxButtons.OK, MessageBoxIcon.Error); + internal static void InfoMessage(string message) => ShowMessageBox(message, Res.TitleInformation, MessageBoxButtons.OK, MessageBoxIcon.Information); + internal static void WarningMessage(string message) => ShowMessageBox(message, Res.TitleWarning, MessageBoxButtons.OK, MessageBoxIcon.Warning); internal static bool ConfirmMessage(string message, bool isYesDefault = true) - => Show(message, Res.TitleConfirmation, MessageBoxButtons.YesNo, MessageBoxIcon.Question, isYesDefault ? MessageBoxDefaultButton.Button1 : MessageBoxDefaultButton.Button2) == DialogResult.Yes; + => ShowMessageBox(message, Res.TitleConfirmation, MessageBoxButtons.YesNo, MessageBoxIcon.Question, isYesDefault ? MessageBoxDefaultButton.Button1 : MessageBoxDefaultButton.Button2) == DialogResult.Yes; internal static bool? CancellableConfirmMessage(string message, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1) - => Show(message, Res.TitleConfirmation, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, defaultButton) switch + => ShowMessageBox(message, Res.TitleConfirmation, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, defaultButton) switch { DialogResult.Yes => true, DialogResult.No => false, _ => null }; + internal static Color? PickColor(Color? selectedColor = default) + { + colorDialog ??= new ColorDialog { /*AnyColor = true,*/ FullOpen = true }; + if (selectedColor.HasValue) + colorDialog.Color = selectedColor.Value; + + // On Windows hooking messages to be able to localize the dialog texts + IntPtr windowHook = IntPtr.Zero; + if (OSUtils.IsWindows && !OSUtils.IsMono) + { + windowHook = User32.HookCallWndRetProc(callWndRetProc); + dialogContext = new DialogContext + { + DialogType = DialogType.ColorDialog, + AllowCustomStaticLocalization = true + }; + } + + DialogResult result = colorDialog.ShowDialog(); + + if (windowHook != IntPtr.Zero) + User32.UnhookWindowsHook(windowHook); + + return result == DialogResult.OK ? colorDialog.Color : null; + } + + internal static string? SelectFolder(string? selectedPath = null) + { + folderDialog ??= new FolderBrowserDialog { ShowNewFolderButton = true }; + if (selectedPath != null) + folderDialog.SelectedPath = selectedPath; + + // On Windows hooking messages to be able to localize the dialog texts + IntPtr windowHook = IntPtr.Zero; + if (OSUtils.IsWindows && !OSUtils.IsMono) + { + windowHook = User32.HookCallWndRetProc(callWndRetProc); + dialogContext = new DialogContext + { + DialogType = DialogType.FolderDialog, + AllowCustomStaticLocalization = true + }; + } + + DialogResult result = folderDialog.ShowDialog(); + + if (windowHook != IntPtr.Zero) + User32.UnhookWindowsHook(windowHook); + + return result == DialogResult.OK ? folderDialog.SelectedPath : null; + } + #endregion #region Private Methods - private static DialogResult Show(string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1) - => MessageBox.Show(message, caption, buttons, icon, defaultButton, LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft ? MessageBoxOptions.RightAlign | MessageBoxOptions.RtlReading : default); + private static DialogResult ShowMessageBox(string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1) + { + MessageBoxOptions options = LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft ? MessageBoxOptions.RightAlign | MessageBoxOptions.RtlReading : default; + IntPtr windowHook = IntPtr.Zero; + + // On Windows hooking messages to be able to localize the buttons + if (OSUtils.IsWindows && !OSUtils.IsMono) + { + windowHook = User32.HookCallWndRetProc(callWndRetProc); + dialogContext = new DialogContext + { + DialogType = buttons == MessageBoxButtons.OK ? DialogType.MessageBoxSingleButton : DialogType.MessageBoxMoreButtons + }; + } + + DialogResult result = MessageBox.Show(message, caption, buttons, icon, defaultButton, options); + + if (windowHook != IntPtr.Zero) + User32.UnhookWindowsHook(windowHook); + + return result; + } private static IntPtr CallWndRetProc(int nCode, IntPtr wParam, IntPtr lParam) { @@ -86,7 +179,16 @@ private static IntPtr CallWndRetProc(int nCode, IntPtr wParam, IntPtr lParam) { string name = User32.GetClassName(msg.hwnd); if (name == Constants.ClassNameDialogBox) + { + // Localizing non-MessageBox captions + if (dialogContext.DialogType == DialogType.ColorDialog) + User32.SetControlText(msg.hwnd, Res.TitleColorDialog); + else if (dialogContext.DialogType == DialogType.FolderDialog) + User32.SetControlText(msg.hwnd, Res.TitleFolderDialog); + + // Enumerating the child controls by another WinAPI call User32.EnumChildWindows(msg.hwnd, enumChildProc); + } } } @@ -97,19 +199,26 @@ private static bool EnumChildProc(IntPtr hWnd, IntPtr lParam) { string className = User32.GetClassName(hWnd); int id = User32.GetDialogControlId(hWnd); - User32.SetWindowText(hWnd, $"{className}.{id}"); - return true; - } - #endregion - - #region Event Handlers - - private static void CurrentDomain_ProcessExit(object sender, EventArgs e) - { - Debug.Assert(windowHook != IntPtr.Zero); - if (windowHook != IntPtr.Zero) - User32.UnhookWindowsHook(windowHook); + // Controls with id 65535 may duplicate on some dialogs. Usually these contain custom message but on color dialog + // these are also constant labels so we assign an incremental id for these. + if (id == UInt16.MaxValue && className == Constants.ClassNameStatic) + { + if (!dialogContext.AllowCustomStaticLocalization) + return true; + className = $"{className}.{dialogContext.DialogType}"; + id = dialogContext.CustomStaticId; + dialogContext.CustomStaticId += 1; + } + // If there is a single OK button in a MessageBox it has the same id as a Cancel button. + else if (dialogContext.DialogType == DialogType.MessageBoxSingleButton && id == Constants.IDCANCEL && className == Constants.ClassNameButton) + id = Constants.IDOK; + + // Now the resource id is either "className.id" or "className.DialogType.id" + string? text = Res.GetStringOrNull($"{className}.{id}"); + if (text != null) + User32.SetControlText(hWnd, text); + return true; } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index 3c79b10..5a219ba 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -121,10 +121,8 @@ protected override void ApplyResources() protected override void ApplyStringResources() { base.ApplyStringResources(); - - // base cannot handle these because components do not have names and dialogs are not even added to components field - dlgOpen.Title = Res.Get($"{nameof(dlgOpen)}.{nameof(dlgOpen.Title)}"); - dlgSave.Title = Res.Get($"{nameof(dlgSave)}.{nameof(dlgSave.Title)}"); + dlgOpen.Title = Res.TitleOpenFileDialog; + dlgSave.Title = Res.TitleSaveFileDialog; } protected override void ApplyViewModel() diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs index 4753bad..05b0985 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs @@ -17,7 +17,6 @@ #region Usings using System.Collections.Generic; -using System.Windows.Forms; using KGySoft.Drawing.ImagingTools.ViewModel; @@ -114,11 +113,7 @@ private void InitCommandBindings() .AddSource(btnRemove, nameof(btnRemove.Click)); } - private string? SelectFolder() - { - using (var dlg = new FolderBrowserDialog { SelectedPath = ViewModel.CurrentPath }) - return dlg.ShowDialog() != DialogResult.OK ? null : dlg.SelectedPath; - } + private string? SelectFolder() => Dialogs.SelectFolder(ViewModel.CurrentPath); #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs index c757b22..4065440 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs @@ -35,7 +35,6 @@ private void InitializeComponent() this.tsMenu = new KGySoft.Drawing.ImagingTools.View.Controls.AdvancedToolStrip(); this.btnSelectColor = new System.Windows.Forms.ToolStripButton(); this.txtColor = new System.Windows.Forms.TextBox(); - this.colorDialog = new System.Windows.Forms.ColorDialog(); this.pnlControls.SuspendLayout(); this.tblColor.SuspendLayout(); this.pnlRed.SuspendLayout(); @@ -258,11 +257,6 @@ private void InitializeComponent() this.txtColor.TabIndex = 1; this.txtColor.WordWrap = false; // - // colorDialog - // - this.colorDialog.AnyColor = true; - this.colorDialog.FullOpen = true; - // // ColorVisualizerControl // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -310,7 +304,6 @@ private void InitializeComponent() private System.Windows.Forms.Panel pnlAlpha; private System.Windows.Forms.ToolStripButton btnSelectColor; private System.Windows.Forms.TextBox txtColor; - private System.Windows.Forms.ColorDialog colorDialog; private System.Windows.Forms.TrackBar tbAlpha; private System.Windows.Forms.TrackBar tbRed; private System.Windows.Forms.TrackBar tbGreen; diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs index 92945e0..50c967e 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs @@ -297,10 +297,10 @@ private void btnEdit_Click(object? sender, EventArgs e) if (readOnly) return; - colorDialog.Color = color; - if (colorDialog.ShowDialog(this) == DialogResult.OK) + Color? newColor = Dialogs.PickColor(color); + if (newColor.HasValue) { - Color = colorDialog.Color; + Color = newColor.Value; OnColorEdited(); } } diff --git a/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs b/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs index 4cf8cae..0474e58 100644 --- a/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs +++ b/KGySoft.Drawing.ImagingTools/WinApi/Constants.cs @@ -33,8 +33,12 @@ internal static class Constants internal const int WH_CALLWNDPROCRET = 12; + internal const int IDOK = 1; + internal const int IDCANCEL = 2; + internal const string ClassNameDialogBox = "#32770"; internal const string ClassNameButton = "Button"; + internal const string ClassNameStatic = "Static"; // ReSharper restore InconsistentNaming #endregion diff --git a/KGySoft.Drawing.ImagingTools/WinApi/User32.cs b/KGySoft.Drawing.ImagingTools/WinApi/User32.cs index 42173e4..fa7645a 100644 --- a/KGySoft.Drawing.ImagingTools/WinApi/User32.cs +++ b/KGySoft.Drawing.ImagingTools/WinApi/User32.cs @@ -23,7 +23,6 @@ #endif using System.Runtime.InteropServices; using System.Security; -using System.Text; #if !NET5_0_OR_GREATER using System.Windows.Forms; #endif @@ -184,7 +183,7 @@ internal static string GetClassName(IntPtr handle) internal static int GetDialogControlId(IntPtr handle) => NativeMethods.GetDlgCtrlID(handle); - internal static bool SetWindowText(IntPtr handle, string text) => NativeMethods.SetWindowText(handle, text); + internal static void SetControlText(IntPtr handle, string text) => NativeMethods.SetWindowText(handle, text); #endregion } From 04a89c17d15ac4fd4357a7453fbd4e4367487e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 20 Jun 2021 16:34:05 +0200 Subject: [PATCH 132/211] Editing resources: improving accents ignore --- .../ViewModel/EditResourcesViewModel.cs | 8 +++-- .../_Extensions/StringExtensions.cs | 32 +++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/_Extensions/StringExtensions.cs diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index 079dfe1..603bae5 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -212,7 +212,7 @@ private void ApplyFilter(IList set) } Debug.Assert(set is SortableBindingList, "Non-empty set is expected to be a SortableBindingList"); - string filter = Filter; + string filter = Filter.StripAccents(); SortableBindingList newSet; if (filter.Length == 0) newSet = (SortableBindingList)set; @@ -226,10 +226,12 @@ private void ApplyFilter(IList set) // Using ordinal search for key, invariant for original text (to allow ignoring char width, for example), // and both ordinal and culture-specific search for the translated text because culture specific fails to match some patterns, // eg.: "Vissza" is not found with the search term "viss" using the Hungarian culture. + // Stripping is because IgnoreNonSpace fails with some accents, eg. "ö" matches "o" using German culture but does not match with Hungarian. + string strippedTranslated; if (entry.Key.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0 || invariantInfo.IndexOf(entry.OriginalText, filter, cultureSpecificCompareOptions) >= 0 - || entry.TranslatedText.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0 - || cultureSpecificInfo.IndexOf(entry.TranslatedText, filter, cultureSpecificCompareOptions) >= 0) + || (strippedTranslated = entry.TranslatedText.StripAccents()).IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0 + || cultureSpecificInfo.IndexOf(strippedTranslated, filter, cultureSpecificCompareOptions) >= 0) { newSet.Add(entry); } diff --git a/KGySoft.Drawing.ImagingTools/_Extensions/StringExtensions.cs b/KGySoft.Drawing.ImagingTools/_Extensions/StringExtensions.cs new file mode 100644 index 0000000..af0ebff --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/_Extensions/StringExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KGySoft.Drawing.ImagingTools +{ + internal static class StringExtensions + { + /// + /// Removes accents from strings for better chances to match a filter pattern when searching. + /// + internal static string StripAccents(this string s) + { + string decomposed = s.Normalize(NormalizationForm.FormKD); + int len = decomposed.Length; + var stripped = new StringBuilder(len); + for (int i = 0; i < len; i++) + { + UnicodeCategory category = CharUnicodeInfo.GetUnicodeCategory(decomposed[i]); + if (category != UnicodeCategory.NonSpacingMark) + stripped.Append(decomposed[i]); + } + + // Note: the string is returned in a decomposed form, which is OK for searching but not for displaying it. + // If it had to be displayed, then a recombining normalization would also be necessary. + return stripped.ToString(); + } + } +} From a4b5df45e16ef815dbae70f064313b8e6bc3e987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 20 Jun 2021 17:14:12 +0200 Subject: [PATCH 133/211] Language settings: fixing unsaved configurations issue if the language was applied during editing resources but the settings dialog was then canceled --- .../ViewModel/LanguageSettingsViewModel.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index b3029c1..3e61571 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -149,7 +149,15 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) } } - protected override void ApplyDisplayLanguage() => UpdateApplyCommandState(); + protected override void ApplyDisplayLanguage() + { + // We are saving configuration when new display language has been applied. + // We do this indirectly because we have to save the resources even if the new language is applied from editing resources. + // Otherwise, it would be possible to select a language without applying it, then editing the resources and applying the new language there, + // in which case the configuration may remain unsaved. + SaveConfiguration(); + UpdateApplyCommandState(); + } protected override void Dispose(bool disposing) { @@ -205,10 +213,11 @@ private void ApplyAndSave() if (!IsModified) return; - // 1. ) Applying the current language + // Applying the current language CultureInfo currentLanguage = CurrentLanguage; LanguageSettings.DynamicResourceManagersSource = AllowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; + // This will trigger SaveConfiguration, too if (Equals(LanguageSettings.DisplayLanguage, currentLanguage)) ResHelper.RaiseLanguageChanged(); else @@ -219,8 +228,10 @@ private void ApplyAndSave() ResHelper.SavePendingResources(); availableResXLanguages = null; selectableLanguages = null; + } - // 2.) Saving the configuration + private void SaveConfiguration() + { Configuration.AllowResXResources = AllowResXResources; Configuration.UseOSLanguage = UseOSLanguage; Configuration.DisplayLanguage = CurrentLanguage; From 0b23c77b9e981305e27006f89d6aea6cffdc72cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 20 Jun 2021 18:44:13 +0200 Subject: [PATCH 134/211] Replacing the ProgressStatusStrip to ProgressFooter, derived from Panel instead of StatusStrip to fix some apparance issues --- .../Controls/DownloadProgressStatusStrip.cs | 2 +- .../Controls/DrawingProgressStatusStrip.cs | 2 +- ...ogressStatusStrip.cs => ProgressFooter.cs} | 70 ++++++++----------- .../Controls/ProgressStatusStrip.Designer.cs | 51 -------------- .../View/Forms/CountColorsForm.Designer.cs | 1 - .../Forms/DownloadResourcesForm.Designer.cs | 1 - .../Forms/TransformBitmapFormBase.Designer.cs | 1 - 7 files changed, 31 insertions(+), 97 deletions(-) rename KGySoft.Drawing.ImagingTools/View/Controls/{ProgressStatusStrip.cs => ProgressFooter.cs} (65%) delete mode 100644 KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.Designer.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressStatusStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressStatusStrip.cs index c0bef7f..e3ce365 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressStatusStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressStatusStrip.cs @@ -23,7 +23,7 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls { - internal class DownloadProgressStatusStrip : ProgressStatusStrip<(int MaximumValue, int CurrentValue)> + internal class DownloadProgressStatusStrip : ProgressFooter<(int MaximumValue, int CurrentValue)> { #region Properties diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs index 31dd700..d240b9f 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs @@ -22,7 +22,7 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls { - internal class DrawingProgressStatusStrip : ProgressStatusStrip + internal class DrawingProgressStatusStrip : ProgressFooter { #region Fields diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs similarity index 65% rename from KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.cs rename to KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs index 195cc24..2440df0 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs @@ -1,7 +1,7 @@ #region Copyright /////////////////////////////////////////////////////////////////////////////// -// File: ProgressStatusStrip.cs +// File: ProgressFooter.cs /////////////////////////////////////////////////////////////////////////////// // Copyright (C) KGy SOFT, 2005-2020 - All Rights Reserved // @@ -18,6 +18,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Drawing; using System.Windows.Forms; #endregion @@ -25,14 +26,17 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls { /// - /// A with a progress bar that can be updated from any thread. + /// A with a progress bar that can be updated from any thread. /// - internal partial class ProgressStatusStrip : StatusStrip + internal class ProgressFooter : AutoMirrorPanel { #region Fields private readonly object syncRoot = new object(); private readonly bool visualStyles = Application.RenderWithVisualStyles; + private readonly Label lblProgress; + private readonly ProgressBar pbProgress; + private readonly Timer timer; private bool progressVisible = true; // so ctor change will have effect at run-time private TProgress? progress; @@ -59,26 +63,7 @@ internal virtual bool ProgressVisible UpdateProgress(); } - // In Windows we don't make label invisible but changing text to a space to prevent status strip height change - if (OSUtils.IsWindows) - { - pbProgress.Visible = progressVisible; - if (!progressVisible) - lblProgress.Text = " "; - } - // On Linux we let the progress bar remain visible for the same reason and to prevent (sort of) appearing ugly thick black areas - else - { - lblProgress.Visible = progressVisible; - if (!progressVisible) - { - pbProgress.Style = ProgressBarStyle.Blocks; - pbProgress.Value = 0; - } - - AdjustSize(); - } - + lblProgress.Visible = pbProgress.Visible = value; timer.Enabled = value; } } @@ -124,16 +109,30 @@ protected int Value #region Constructors - public ProgressStatusStrip() + protected ProgressFooter() { - InitializeComponent(); + Dock = DockStyle.Bottom; + Padding = new Padding(3); + lblProgress = new Label + { + Name = nameof(lblProgress), + Dock = DockStyle.Left, + TextAlign = ContentAlignment.MiddleLeft + }; + pbProgress = new ProgressBar + { + Name = nameof(pbProgress), + Dock = DockStyle.Fill, + RightToLeftLayout = true, + }; + Controls.AddRange(new Control[] { pbProgress, lblProgress }); + timer = new Timer { Interval = 30 }; + if (DesignMode) return; - this.FixAppearance(); + ProgressVisible = false; - SizeChanged += DrawingProgressStatusStrip_SizeChanged; lblProgress.TextChanged += lblProgress_TextChanged; - lblProgress.VisibleChanged += lblProgress_VisibleChanged; timer.Tick += timer_Tick; } @@ -146,30 +145,19 @@ public ProgressStatusStrip() protected override void Dispose(bool disposing) { if (disposing) - components?.Dispose(); + timer.Dispose(); - SizeChanged -= DrawingProgressStatusStrip_SizeChanged; lblProgress.TextChanged -= lblProgress_TextChanged; - lblProgress.VisibleChanged -= lblProgress_VisibleChanged; timer.Tick -= timer_Tick; base.Dispose(disposing); } #endregion - #region Private Methods - - private void AdjustSize() => - pbProgress.Width = Width - (lblProgress.Visible ? lblProgress.Width - lblProgress.Margin.Horizontal : 0) - pbProgress.Margin.Horizontal - 2; - - #endregion - #region Event handlers #pragma warning disable IDE1006 // Naming Styles - private void lblProgress_TextChanged(object? sender, EventArgs e) => AdjustSize(); - private void lblProgress_VisibleChanged(object? sender, EventArgs e) => AdjustSize(); - private void DrawingProgressStatusStrip_SizeChanged(object? sender, EventArgs e) => AdjustSize(); + private void lblProgress_TextChanged(object sender, EventArgs e) => lblProgress.Width = lblProgress.PreferredWidth; private void timer_Tick(object? sender, EventArgs e) => UpdateProgress(); diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.Designer.cs deleted file mode 100644 index e4219a3..0000000 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ProgressStatusStrip.Designer.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Windows.Forms; - -namespace KGySoft.Drawing.ImagingTools.View.Controls -{ - partial class ProgressStatusStrip - { - private void InitializeComponent() - { - this.components = new System.ComponentModel.Container(); - this.lblProgress = new System.Windows.Forms.ToolStripStatusLabel(); - this.pbProgress = new System.Windows.Forms.ToolStripProgressBar(); - this.timer = new System.Windows.Forms.Timer(this.components); - this.SuspendLayout(); - // - // lblProgress - // - this.lblProgress.Name = "lblProgress"; - this.lblProgress.Size = new System.Drawing.Size(0, 17); - // - // pbProgress - // - this.pbProgress.AutoSize = false; - this.pbProgress.Name = "pbProgress"; - this.pbProgress.RightToLeftLayout = true; - this.pbProgress.Size = new System.Drawing.Size(100, 16); - // - // timer - // - this.timer.Interval = 30; - // - // DrawingProgressStatusStrip - // - this.BackColor = System.Drawing.Color.Transparent; - this.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.lblProgress, - this.pbProgress}); - this.SizingGrip = false; - this.ResumeLayout(false); - - } - - private ToolStripStatusLabel lblProgress; - private ToolStripProgressBar pbProgress; - private Timer timer; - private System.ComponentModel.IContainer components; - } -} diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.Designer.cs index d9e46bb..57a4145 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.Designer.cs @@ -59,7 +59,6 @@ private void InitializeComponent() this.progress.Location = new System.Drawing.Point(0, 68); this.progress.Name = "progress"; this.progress.Size = new System.Drawing.Size(274, 22); - this.progress.SizingGrip = false; this.progress.TabIndex = 2; this.progress.Text = "drawingProgressStatusStrip1"; // diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs index 88286a8..dcf408e 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs @@ -104,7 +104,6 @@ private void InitializeComponent() this.progress.Location = new System.Drawing.Point(3, 201); this.progress.Name = "progress"; this.progress.Size = new System.Drawing.Size(358, 22); - this.progress.SizingGrip = false; this.progress.TabIndex = 2; this.progress.Text = "drawingProgressStatusStrip1"; // diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs index fe2fa62..dfc4afe 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs @@ -28,7 +28,6 @@ private void InitializeComponent() this.progress.Location = new System.Drawing.Point(0, 189); this.progress.Name = "progress"; this.progress.Size = new System.Drawing.Size(248, 22); - this.progress.SizingGrip = false; this.progress.TabIndex = 3; this.progress.Text = "drawingProgressStatusStrip1"; // From 3664ae08fc9ad37ff3f43e5a0aa959f024ad9699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 20 Jun 2021 18:56:16 +0200 Subject: [PATCH 135/211] Fixing appearance --- .../Forms/PaletteVisualizerForm.Designer.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.Designer.cs index 0a8d7a1..50b2304 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.Designer.cs @@ -19,7 +19,7 @@ partial class PaletteVisualizerForm private void InitializeComponent() { this.gbPalette = new System.Windows.Forms.GroupBox(); - this.pnlPalette = new PalettePanel(); + this.pnlPalette = new KGySoft.Drawing.ImagingTools.View.Controls.PalettePanel(); this.gbSelectedColor = new System.Windows.Forms.GroupBox(); this.ucColorVisualizer = new KGySoft.Drawing.ImagingTools.View.UserControls.ColorVisualizerControl(); this.okCancelButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); @@ -31,9 +31,9 @@ private void InitializeComponent() // this.gbPalette.Controls.Add(this.pnlPalette); this.gbPalette.Dock = System.Windows.Forms.DockStyle.Fill; - this.gbPalette.Location = new System.Drawing.Point(0, 0); + this.gbPalette.Location = new System.Drawing.Point(3, 3); this.gbPalette.Name = "gbPalette"; - this.gbPalette.Size = new System.Drawing.Size(247, 234); + this.gbPalette.Size = new System.Drawing.Size(241, 233); this.gbPalette.TabIndex = 0; this.gbPalette.TabStop = false; // @@ -42,17 +42,16 @@ private void InitializeComponent() this.pnlPalette.Dock = System.Windows.Forms.DockStyle.Fill; this.pnlPalette.Location = new System.Drawing.Point(3, 16); this.pnlPalette.Name = "pnlPalette"; - this.pnlPalette.Size = new System.Drawing.Size(241, 215); + this.pnlPalette.Size = new System.Drawing.Size(235, 214); this.pnlPalette.TabIndex = 0; - this.pnlPalette.TabStop = true; // // gbSelectedColor // this.gbSelectedColor.Controls.Add(this.ucColorVisualizer); this.gbSelectedColor.Dock = System.Windows.Forms.DockStyle.Bottom; - this.gbSelectedColor.Location = new System.Drawing.Point(0, 234); + this.gbSelectedColor.Location = new System.Drawing.Point(3, 236); this.gbSelectedColor.Name = "gbSelectedColor"; - this.gbSelectedColor.Size = new System.Drawing.Size(247, 216); + this.gbSelectedColor.Size = new System.Drawing.Size(241, 216); this.gbSelectedColor.TabIndex = 1; this.gbSelectedColor.TabStop = false; // @@ -61,15 +60,15 @@ private void InitializeComponent() this.ucColorVisualizer.Dock = System.Windows.Forms.DockStyle.Fill; this.ucColorVisualizer.Location = new System.Drawing.Point(3, 16); this.ucColorVisualizer.Name = "ucColorVisualizer"; - this.ucColorVisualizer.Size = new System.Drawing.Size(241, 197); + this.ucColorVisualizer.Size = new System.Drawing.Size(235, 197); this.ucColorVisualizer.TabIndex = 0; // // okCancelButtons // this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; - this.okCancelButtons.Location = new System.Drawing.Point(0, 450); + this.okCancelButtons.Location = new System.Drawing.Point(3, 452); this.okCancelButtons.Name = "okCancelButtons"; - this.okCancelButtons.Size = new System.Drawing.Size(247, 35); + this.okCancelButtons.Size = new System.Drawing.Size(241, 35); this.okCancelButtons.TabIndex = 2; // // PaletteVisualizerForm @@ -84,6 +83,7 @@ private void InitializeComponent() this.MaximumSize = new System.Drawing.Size(280, 32867); this.MinimumSize = new System.Drawing.Size(255, 335); this.Name = "PaletteVisualizerForm"; + this.Padding = new System.Windows.Forms.Padding(3); this.gbPalette.ResumeLayout(false); this.gbSelectedColor.ResumeLayout(false); this.ResumeLayout(false); From dab53bc3b45ed5f70df5f5789b2c8ee78ada1d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 20 Jun 2021 21:15:42 +0200 Subject: [PATCH 136/211] Auto size for localizable elements, adjusting resources --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 13 ++++++---- KGySoft.Drawing.ImagingTools/Res.cs | 13 ++++++---- .../View/Forms/AppMainForm.cs | 2 +- .../Forms/ManageInstallationsForm.Designer.cs | 24 +++++++++---------- .../UserControls/OkCancelButtons.Designer.cs | 4 ++++ .../ViewModel/DownloadResourcesViewModel.cs | 5 ---- 6 files changed, 33 insertions(+), 28 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 5ff5544..27f354f 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -123,6 +123,9 @@ KGy SOFT Imaging Tools v{0} + + {0} [{1}{2}] – {3} + No Image @@ -172,7 +175,7 @@ Color: {0} - Edit Resources - {0} + Edit Resources – {0} files @@ -574,19 +577,19 @@ Copyright © {2} KGy SOFT. All rights reserved. Debugger version: {0} - Debugger version: {0} - Runtime: {1} + Debugger version: {0} – Runtime: {1} - Debugger version: {0} - Target: {1} + Debugger version: {0} – Target: {1} Installed: {0} - Installed: {0} - Runtime: {1} + Installed: {0} – Runtime: {1} - Installed: {0} - Target: {1} + Installed: {0} – Target: {1} Auto Zoom (Alt+Z) / Zoom Options diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 1fe4a55..95e8432 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -343,6 +343,9 @@ internal static void ApplyStringResources(object target, string name) /// KGy SOFT Imaging Tools v{0} internal static string TitleAppNameAndVersion(Version version) => Get("Title_AppNameAndVersionFormat", version); + /// {0} [{1}{2}] – {3} + internal static string TitleAppNameWithFileName(string title, string fileName, string modifiedMark, string caption) => Get("Title_AppNameWithFileNameFormat", title, fileName, modifiedMark, caption); + /// Type: {0} internal static string TitleType(string type) => Get("Title_TypeFormat", type); @@ -361,7 +364,7 @@ internal static void ApplyStringResources(object target, string name) /// Color: {0} internal static string TitleColor(Color color) => Get("Title_ColorFormat", color.Name); - /// Edit Resources - {0} + /// Edit Resources – {0} internal static string TitleEditResources(string langName) => Get("Title_EditResourcesFormat", langName); #endregion @@ -625,19 +628,19 @@ internal static string InfoColor(int argb, string knownColors, string systemColo /// Debugger version: {0} internal static string InstallationAvailable(Version version) => Get("Installation_AvailableFormat", version); - /// Debugger version: {0} - Runtime: {1} + /// Debugger version: {0} – Runtime: {1} internal static string InstallationsAvailableWithRuntime(Version version, string runtimeVersion) => Get("Installation_AvailableWithRuntimeFormat", version, runtimeVersion); - /// Debugger version: {0} - Target: {1} + /// Debugger version: {0} – Target: {1} internal static string InstallationsAvailableWithTargetFramework(Version version, string targetFramework) => Get("Installation_AvailableWithTargetFrameworkFormat", version, targetFramework); /// Installed: {0} internal static string InstallationsStatusInstalled(Version version) => Get("Installations_StatusInstalledFormat", version); - /// Installed: {0} - Runtime: {1} + /// Installed: {0} – Runtime: {1} internal static string InstallationsStatusInstalledWithRuntime(Version version, string runtimeVersion) => Get("Installations_StatusInstalledWithRuntimeFormat", version, runtimeVersion); - /// Installed: {0} - Target: {1} + /// Installed: {0} – Target: {1} internal static string InstallationsStatusInstalledWithTargetFramework(Version version, string targetFramework) => Get("Installations_StatusInstalledWithTargetFrameworkFormat", version, targetFramework); #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs index c87fb49..1506a91 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs @@ -134,7 +134,7 @@ private string FormatText(string? value) { string? fileName = ViewModel.FileName; string name = fileName == null ? Res.TextUnnamed : Path.GetFileName(fileName); - return String.IsNullOrEmpty(value) ? title! : $"{title} [{name}{(ViewModel.IsModified ? "*" : String.Empty)}] - {value}"; + return String.IsNullOrEmpty(value) ? title! : Res.TitleAppNameWithFileName(title!, name, ViewModel.IsModified ? "*" : String.Empty, value!); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs index 9ee62de..10e423e 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs @@ -39,14 +39,14 @@ private void InitializeComponent() // // gbInstallation // - this.gbInstallation.Controls.Add(this.tblButtons); this.gbInstallation.Controls.Add(this.pnlStatus); + this.gbInstallation.Controls.Add(this.tblButtons); this.gbInstallation.Controls.Add(this.tbPath); this.gbInstallation.Controls.Add(this.lblPath); this.gbInstallation.Dock = System.Windows.Forms.DockStyle.Fill; this.gbInstallation.Location = new System.Drawing.Point(3, 89); this.gbInstallation.Name = "gbInstallation"; - this.gbInstallation.Size = new System.Drawing.Size(378, 105); + this.gbInstallation.Size = new System.Drawing.Size(378, 119); this.gbInstallation.TabIndex = 2; this.gbInstallation.TabStop = false; this.gbInstallation.Text = "gbInstallation"; @@ -58,8 +58,8 @@ private void InitializeComponent() this.tblButtons.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); this.tblButtons.Controls.Add(this.btnRemove, 1, 0); this.tblButtons.Controls.Add(this.btnInstall, 0, 0); - this.tblButtons.Dock = System.Windows.Forms.DockStyle.Top; - this.tblButtons.Location = new System.Drawing.Point(3, 66); + this.tblButtons.Dock = System.Windows.Forms.DockStyle.Bottom; + this.tblButtons.Location = new System.Drawing.Point(3, 79); this.tblButtons.Name = "tblButtons"; this.tblButtons.RowCount = 1; this.tblButtons.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); @@ -93,21 +93,21 @@ private void InitializeComponent() // this.pnlStatus.Controls.Add(this.lblStatusText); this.pnlStatus.Controls.Add(this.lblStatus); - this.pnlStatus.Dock = System.Windows.Forms.DockStyle.Top; + this.pnlStatus.Dock = System.Windows.Forms.DockStyle.Fill; this.pnlStatus.Location = new System.Drawing.Point(3, 49); this.pnlStatus.Name = "pnlStatus"; - this.pnlStatus.Size = new System.Drawing.Size(372, 17); + this.pnlStatus.Padding = new System.Windows.Forms.Padding(0, 3, 0, 0); + this.pnlStatus.Size = new System.Drawing.Size(372, 30); this.pnlStatus.TabIndex = 2; // // lblStatusText // - this.lblStatusText.AutoSize = true; - this.lblStatusText.Dock = System.Windows.Forms.DockStyle.Left; + this.lblStatusText.Dock = System.Windows.Forms.DockStyle.Fill; this.lblStatusText.FlatStyle = System.Windows.Forms.FlatStyle.System; this.lblStatusText.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(238))); - this.lblStatusText.Location = new System.Drawing.Point(47, 0); + this.lblStatusText.Location = new System.Drawing.Point(47, 3); this.lblStatusText.Name = "lblStatusText"; - this.lblStatusText.Size = new System.Drawing.Size(81, 13); + this.lblStatusText.Size = new System.Drawing.Size(325, 27); this.lblStatusText.TabIndex = 1; this.lblStatusText.Text = "lblStatusText"; // @@ -116,7 +116,7 @@ private void InitializeComponent() this.lblStatus.AutoSize = true; this.lblStatus.Dock = System.Windows.Forms.DockStyle.Left; this.lblStatus.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.lblStatus.Location = new System.Drawing.Point(0, 0); + this.lblStatus.Location = new System.Drawing.Point(0, 3); this.lblStatus.Name = "lblStatus"; this.lblStatus.Size = new System.Drawing.Size(47, 13); this.lblStatus.TabIndex = 0; @@ -190,7 +190,7 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(384, 197); + this.ClientSize = new System.Drawing.Size(384, 211); this.Controls.Add(this.gbInstallation); this.Controls.Add(this.gbVisualStudioVersions); this.Controls.Add(this.gbAvailableVersion); diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs index ea71a13..864f2ea 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs @@ -38,6 +38,7 @@ private void InitializeComponent() // btnApply // this.btnApply.Anchor = System.Windows.Forms.AnchorStyles.None; + this.btnApply.AutoSize = true; this.btnApply.FlatStyle = System.Windows.Forms.FlatStyle.System; this.btnApply.Location = new System.Drawing.Point(176, 6); this.btnApply.Name = "btnApply"; @@ -50,6 +51,7 @@ private void InitializeComponent() // btnCancel // this.btnCancel.Anchor = System.Windows.Forms.AnchorStyles.None; + this.btnCancel.AutoSize = true; this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; this.btnCancel.FlatStyle = System.Windows.Forms.FlatStyle.System; this.btnCancel.Location = new System.Drawing.Point(95, 6); @@ -62,6 +64,7 @@ private void InitializeComponent() // btnOK // this.btnOK.Anchor = System.Windows.Forms.AnchorStyles.None; + this.btnOK.AutoSize = true; this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK; this.btnOK.FlatStyle = System.Windows.Forms.FlatStyle.System; this.btnOK.Location = new System.Drawing.Point(14, 6); @@ -79,6 +82,7 @@ private void InitializeComponent() this.Name = "OkCancelButtons"; this.Size = new System.Drawing.Size(260, 35); this.pnlButtons.ResumeLayout(false); + this.pnlButtons.PerformLayout(); this.ResumeLayout(false); } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index bc43fc1..5cc5bda 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -312,11 +312,6 @@ private void DoDownloadResources(object state) } } - private void ResetDownloadCommandState() - { - throw new NotImplementedException(); - } - private void ApplyResources() { ResHelper.ReleaseAllResources(); From b7ba5ba2b79522e7eee8276b81904e648808bead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 21 Jun 2021 13:32:56 +0200 Subject: [PATCH 137/211] Improving appearance --- ...atusStrip.cs => DownloadProgressFooter.cs} | 4 +- ...tatusStrip.cs => DrawingProgressFooter.cs} | 4 +- .../View/Controls/ProgressFooter.cs | 3 +- .../View/Forms/AppMainForm.Designer.cs | 9 ++++- .../View/Forms/AppMainForm.cs | 1 - .../View/Forms/ColorSpaceForm.Designer.cs | 1 - .../View/Forms/ColorSpaceForm.cs | 2 +- .../View/Forms/CountColorsForm.Designer.cs | 4 +- .../Forms/DownloadResourcesForm.Designer.cs | 4 +- .../Forms/ImageVisualizerForm.Designer.cs | 38 ++++++++++--------- .../View/Forms/ImageVisualizerForm.cs | 2 +- .../View/Forms/ResizeBitmapForm.Designer.cs | 33 ++++++++-------- .../Forms/TransformBitmapFormBase.Designer.cs | 27 ++++++------- .../UserControls/OkCancelButtons.Designer.cs | 3 +- .../View/UserControls/OkCancelButtons.cs | 1 + 15 files changed, 74 insertions(+), 62 deletions(-) rename KGySoft.Drawing.ImagingTools/View/Controls/{DownloadProgressStatusStrip.cs => DownloadProgressFooter.cs} (91%) rename KGySoft.Drawing.ImagingTools/View/Controls/{DrawingProgressStatusStrip.cs => DrawingProgressFooter.cs} (92%) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressStatusStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressFooter.cs similarity index 91% rename from KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressStatusStrip.cs rename to KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressFooter.cs index e3ce365..9be461d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressStatusStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/DownloadProgressFooter.cs @@ -1,7 +1,7 @@ #region Copyright /////////////////////////////////////////////////////////////////////////////// -// File: DownloadProgressStatusStrip.cs +// File: DownloadProgressFooter.cs /////////////////////////////////////////////////////////////////////////////// // Copyright (C) KGy SOFT, 2005-2020 - All Rights Reserved // @@ -23,7 +23,7 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls { - internal class DownloadProgressStatusStrip : ProgressFooter<(int MaximumValue, int CurrentValue)> + internal class DownloadProgressFooter : ProgressFooter<(int MaximumValue, int CurrentValue)> { #region Properties diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressFooter.cs similarity index 92% rename from KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs rename to KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressFooter.cs index d240b9f..c20dfc1 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressStatusStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/DrawingProgressFooter.cs @@ -1,7 +1,7 @@ #region Copyright /////////////////////////////////////////////////////////////////////////////// -// File: DrawingProgressStatusStrip.cs +// File: DrawingProgressFooter.cs /////////////////////////////////////////////////////////////////////////////// // Copyright (C) KGy SOFT, 2005-2020 - All Rights Reserved // @@ -22,7 +22,7 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls { - internal class DrawingProgressStatusStrip : ProgressFooter + internal class DrawingProgressFooter : ProgressFooter { #region Fields diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs index 2440df0..804463e 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs @@ -103,6 +103,8 @@ protected int Value } } + protected override Padding DefaultPadding => new Padding(3, 3, 8, 3); + #endregion #endregion @@ -112,7 +114,6 @@ protected int Value protected ProgressFooter() { Dock = DockStyle.Bottom; - Padding = new Padding(3); lblProgress = new Label { Name = nameof(lblProgress), diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.Designer.cs index e4ce4dc..11dbc58 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.Designer.cs @@ -19,9 +19,15 @@ private void InitializeComponent() // // txtInfo // - this.txtInfo.Location = new System.Drawing.Point(0, 188); + this.txtInfo.Location = new System.Drawing.Point(0, 153); this.txtInfo.Size = new System.Drawing.Size(484, 123); // + // okCancelButtons + // + this.okCancelButtons.Location = new System.Drawing.Point(0, 276); + this.okCancelButtons.Size = new System.Drawing.Size(484, 35); + this.okCancelButtons.Visible = false; + // // AppMainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -29,6 +35,7 @@ private void InitializeComponent() this.ClientSize = new System.Drawing.Size(484, 311); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable; this.Name = "AppMainForm"; + this.Padding = new System.Windows.Forms.Padding(0); this.ResumeLayout(false); this.PerformLayout(); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs index 1506a91..bf5f239 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs @@ -65,7 +65,6 @@ internal AppMainForm(DefaultViewModel viewModel) : base(viewModel) { InitializeComponent(); - okCancelButtons.Visible = false; } #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.Designer.cs index c00f089..3849fff 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.Designer.cs @@ -105,7 +105,6 @@ private void InitializeComponent() this.ClientSize = new System.Drawing.Size(384, 421); this.MinimumSize = new System.Drawing.Size(400, 460); this.Name = "ColorSpaceForm"; - this.Padding = new System.Windows.Forms.Padding(3, 3, 3, 0); this.Text = "ColorSpaceForm"; this.pnlSettings.ResumeLayout(false); this.gbDitherer.ResumeLayout(false); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.cs index c6b866d..18a17f9 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ColorSpaceForm.cs @@ -18,7 +18,7 @@ using System.Linq; using System.Windows.Forms; -using KGySoft.Drawing.ImagingTools.View.Controls; + using KGySoft.Drawing.ImagingTools.ViewModel; #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.Designer.cs index 57a4145..0227f89 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/CountColorsForm.Designer.cs @@ -18,7 +18,7 @@ private void InitializeComponent() this.lblCountColorsStatus = new System.Windows.Forms.Label(); this.pnlButton = new System.Windows.Forms.Panel(); this.btnClose = new System.Windows.Forms.Button(); - this.progress = new KGySoft.Drawing.ImagingTools.View.Controls.DrawingProgressStatusStrip(); + this.progress = new KGySoft.Drawing.ImagingTools.View.Controls.DrawingProgressFooter(); this.pnlButton.SuspendLayout(); this.SuspendLayout(); // @@ -83,7 +83,7 @@ private void InitializeComponent() #endregion - private Controls.DrawingProgressStatusStrip progress; + private Controls.DrawingProgressFooter progress; private System.Windows.Forms.Label lblCountColorsStatus; private System.Windows.Forms.Panel pnlButton; private System.Windows.Forms.Button btnClose; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs index dcf408e..209864c 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/DownloadResourcesForm.Designer.cs @@ -25,7 +25,7 @@ private void InitializeComponent() this.colAuthor = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colImagingToolsVersion = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.colDescription = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.progress = new KGySoft.Drawing.ImagingTools.View.Controls.DownloadProgressStatusStrip(); + this.progress = new KGySoft.Drawing.ImagingTools.View.Controls.DownloadProgressFooter(); ((System.ComponentModel.ISupportInitialize)(this.bindingSource)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.gridDownloadableResources)).BeginInit(); this.SuspendLayout(); @@ -129,7 +129,7 @@ private void InitializeComponent() #endregion - private Controls.DownloadProgressStatusStrip progress; + private Controls.DownloadProgressFooter progress; private Controls.AdvancedDataGridView gridDownloadableResources; private System.Windows.Forms.BindingSource bindingSource; private UserControls.OkCancelButtons okCancelButtons; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs index 215d2f0..6517d50 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs @@ -74,9 +74,9 @@ private void InitializeComponent() // imageViewer // this.imageViewer.Dock = System.Windows.Forms.DockStyle.Fill; - this.imageViewer.Location = new System.Drawing.Point(0, 49); + this.imageViewer.Location = new System.Drawing.Point(3, 49); this.imageViewer.Name = "imageViewer"; - this.imageViewer.Size = new System.Drawing.Size(334, 106); + this.imageViewer.Size = new System.Drawing.Size(328, 106); this.imageViewer.TabIndex = 1; this.imageViewer.TabStop = false; // @@ -88,21 +88,21 @@ private void InitializeComponent() this.lblNotification.Dock = System.Windows.Forms.DockStyle.Top; this.lblNotification.ForeColor = System.Drawing.Color.Black; this.lblNotification.ImageAlign = System.Drawing.ContentAlignment.MiddleRight; - this.lblNotification.Location = new System.Drawing.Point(0, 25); + this.lblNotification.Location = new System.Drawing.Point(3, 25); this.lblNotification.Name = "lblNotification"; this.lblNotification.Padding = new System.Windows.Forms.Padding(3, 3, 20, 3); - this.lblNotification.Size = new System.Drawing.Size(334, 24); + this.lblNotification.Size = new System.Drawing.Size(328, 24); this.lblNotification.TabIndex = 4; this.lblNotification.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // // splitter // this.splitter.Dock = System.Windows.Forms.DockStyle.Bottom; - this.splitter.Location = new System.Drawing.Point(0, 155); + this.splitter.Location = new System.Drawing.Point(3, 155); this.splitter.MinExtra = 16; this.splitter.MinSize = 50; this.splitter.Name = "splitter"; - this.splitter.Size = new System.Drawing.Size(334, 3); + this.splitter.Size = new System.Drawing.Size(328, 3); this.splitter.TabIndex = 3; this.splitter.TabStop = false; // @@ -125,9 +125,9 @@ private void InitializeComponent() this.toolStripSeparator4, this.btnAbout, this.btnConfiguration}); - this.tsMenu.Location = new System.Drawing.Point(0, 0); + this.tsMenu.Location = new System.Drawing.Point(3, 0); this.tsMenu.Name = "tsMenu"; - this.tsMenu.Size = new System.Drawing.Size(334, 25); + this.tsMenu.Size = new System.Drawing.Size(328, 25); this.tsMenu.TabIndex = 2; // // btnZoom @@ -372,37 +372,37 @@ private void InitializeComponent() // miWebSite // this.miWebSite.Name = "miWebSite"; - this.miWebSite.Size = new System.Drawing.Size(180, 22); + this.miWebSite.Size = new System.Drawing.Size(179, 22); this.miWebSite.Text = "miWebSite"; // // miGitHub // this.miGitHub.Name = "miGitHub"; - this.miGitHub.Size = new System.Drawing.Size(180, 22); + this.miGitHub.Size = new System.Drawing.Size(179, 22); this.miGitHub.Text = "miGitHub"; // // miMarketplace // this.miMarketplace.Name = "miMarketplace"; - this.miMarketplace.Size = new System.Drawing.Size(180, 22); + this.miMarketplace.Size = new System.Drawing.Size(179, 22); this.miMarketplace.Text = "miMarketplace"; // // miSubmitResources // this.miSubmitResources.Name = "miSubmitResources"; - this.miSubmitResources.Size = new System.Drawing.Size(180, 22); + this.miSubmitResources.Size = new System.Drawing.Size(179, 22); this.miSubmitResources.Text = "miSubmitResources"; // // miSeparatorAbout // this.miSeparatorAbout.Name = "miSeparatorAbout"; - this.miSeparatorAbout.Size = new System.Drawing.Size(177, 6); + this.miSeparatorAbout.Size = new System.Drawing.Size(176, 6); // // miAbout // this.miAbout.Name = "miAbout"; this.miAbout.ShortcutKeys = System.Windows.Forms.Keys.F1; - this.miAbout.Size = new System.Drawing.Size(180, 22); + this.miAbout.Size = new System.Drawing.Size(179, 22); this.miAbout.Text = "miAbout"; // // btnConfiguration @@ -431,22 +431,23 @@ private void InitializeComponent() // txtInfo // this.txtInfo.Dock = System.Windows.Forms.DockStyle.Bottom; - this.txtInfo.Location = new System.Drawing.Point(0, 158); + this.txtInfo.Location = new System.Drawing.Point(3, 158); this.txtInfo.Multiline = true; this.txtInfo.Name = "txtInfo"; this.txtInfo.ReadOnly = true; this.txtInfo.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.txtInfo.Size = new System.Drawing.Size(334, 123); + this.txtInfo.Size = new System.Drawing.Size(328, 123); this.txtInfo.TabIndex = 0; this.txtInfo.TabStop = false; this.txtInfo.WordWrap = false; // // okCancelButtons // + this.okCancelButtons.BackColor = System.Drawing.Color.Transparent; this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; - this.okCancelButtons.Location = new System.Drawing.Point(0, 281); + this.okCancelButtons.Location = new System.Drawing.Point(3, 281); this.okCancelButtons.Name = "okCancelButtons"; - this.okCancelButtons.Size = new System.Drawing.Size(334, 35); + this.okCancelButtons.Size = new System.Drawing.Size(328, 35); this.okCancelButtons.TabIndex = 5; // // ImageVisualizerForm @@ -463,6 +464,7 @@ private void InitializeComponent() this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; this.MinimumSize = new System.Drawing.Size(200, 200); this.Name = "ImageVisualizerForm"; + this.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.tsMenu.ResumeLayout(false); this.tsMenu.PerformLayout(); this.ResumeLayout(false); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index 5a219ba..d0b9ea6 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -356,7 +356,7 @@ private void AdjustSize() if (imageViewer.Height >= minHeight) return; int buttonsHeight = okCancelButtons.Visible ? okCancelButtons.Height : 0; - txtInfo.Height = ClientSize.Height - tsMenu.Height - splitter.Height - buttonsHeight - minHeight; + txtInfo.Height = ClientSize.Height - Padding.Vertical - tsMenu.Height - splitter.Height - buttonsHeight - minHeight; PerformLayout(); } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs index b2bf727..a343f0d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.Designer.cs @@ -46,8 +46,9 @@ private void InitializeComponent() // pnlSettings // this.pnlSettings.Controls.Add(this.tblNewSize); + this.pnlSettings.Location = new System.Drawing.Point(3, 0); this.pnlSettings.Padding = new System.Windows.Forms.Padding(5); - this.pnlSettings.Size = new System.Drawing.Size(334, 143); + this.pnlSettings.Size = new System.Drawing.Size(328, 143); // // tblNewSize // @@ -75,7 +76,7 @@ private void InitializeComponent() this.tblNewSize.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); this.tblNewSize.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); this.tblNewSize.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tblNewSize.Size = new System.Drawing.Size(324, 137); + this.tblNewSize.Size = new System.Drawing.Size(318, 137); this.tblNewSize.TabIndex = 0; // // chbMaintainAspectRatio @@ -86,7 +87,7 @@ private void InitializeComponent() this.chbMaintainAspectRatio.FlatStyle = System.Windows.Forms.FlatStyle.System; this.chbMaintainAspectRatio.Location = new System.Drawing.Point(103, 3); this.chbMaintainAspectRatio.Name = "chbMaintainAspectRatio"; - this.chbMaintainAspectRatio.Size = new System.Drawing.Size(218, 18); + this.chbMaintainAspectRatio.Size = new System.Drawing.Size(212, 18); this.chbMaintainAspectRatio.TabIndex = 0; this.chbMaintainAspectRatio.Text = "chbMaintainAspectRatio"; this.chbMaintainAspectRatio.UseVisualStyleBackColor = true; @@ -107,10 +108,10 @@ private void InitializeComponent() this.pnlHeightPx.Controls.Add(this.lblHeightPx); this.pnlHeightPx.Controls.Add(this.txtHeightPx); this.pnlHeightPx.Dock = System.Windows.Forms.DockStyle.Top; - this.pnlHeightPx.Location = new System.Drawing.Point(212, 81); + this.pnlHeightPx.Location = new System.Drawing.Point(209, 81); this.pnlHeightPx.Margin = new System.Windows.Forms.Padding(0); this.pnlHeightPx.Name = "pnlHeightPx"; - this.pnlHeightPx.Size = new System.Drawing.Size(112, 21); + this.pnlHeightPx.Size = new System.Drawing.Size(109, 21); this.pnlHeightPx.TabIndex = 8; // // lblHeightPx @@ -119,7 +120,7 @@ private void InitializeComponent() this.lblHeightPx.Location = new System.Drawing.Point(62, 0); this.lblHeightPx.Name = "lblHeightPx"; this.lblHeightPx.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0); - this.lblHeightPx.Size = new System.Drawing.Size(50, 21); + this.lblHeightPx.Size = new System.Drawing.Size(47, 21); this.lblHeightPx.TabIndex = 1; this.lblHeightPx.Text = "lblHeightPx"; // @@ -140,7 +141,7 @@ private void InitializeComponent() this.pnlHeightPercent.Location = new System.Drawing.Point(100, 81); this.pnlHeightPercent.Margin = new System.Windows.Forms.Padding(0); this.pnlHeightPercent.Name = "pnlHeightPercent"; - this.pnlHeightPercent.Size = new System.Drawing.Size(112, 21); + this.pnlHeightPercent.Size = new System.Drawing.Size(109, 21); this.pnlHeightPercent.TabIndex = 7; // // lblHeightPercent @@ -168,10 +169,10 @@ private void InitializeComponent() this.pnlWidthPx.Controls.Add(this.lblWidthPx); this.pnlWidthPx.Controls.Add(this.txtWidthPx); this.pnlWidthPx.Dock = System.Windows.Forms.DockStyle.Top; - this.pnlWidthPx.Location = new System.Drawing.Point(212, 54); + this.pnlWidthPx.Location = new System.Drawing.Point(209, 54); this.pnlWidthPx.Margin = new System.Windows.Forms.Padding(0); this.pnlWidthPx.Name = "pnlWidthPx"; - this.pnlWidthPx.Size = new System.Drawing.Size(112, 21); + this.pnlWidthPx.Size = new System.Drawing.Size(109, 21); this.pnlWidthPx.TabIndex = 5; // // lblWidthPx @@ -180,7 +181,7 @@ private void InitializeComponent() this.lblWidthPx.Location = new System.Drawing.Point(62, 0); this.lblWidthPx.Name = "lblWidthPx"; this.lblWidthPx.Padding = new System.Windows.Forms.Padding(0, 2, 0, 0); - this.lblWidthPx.Size = new System.Drawing.Size(50, 21); + this.lblWidthPx.Size = new System.Drawing.Size(47, 21); this.lblWidthPx.TabIndex = 1; this.lblWidthPx.Text = "lblWidthPx"; // @@ -197,9 +198,9 @@ private void InitializeComponent() // this.rbByPixels.Dock = System.Windows.Forms.DockStyle.Fill; this.rbByPixels.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.rbByPixels.Location = new System.Drawing.Point(215, 30); + this.rbByPixels.Location = new System.Drawing.Point(212, 30); this.rbByPixels.Name = "rbByPixels"; - this.rbByPixels.Size = new System.Drawing.Size(106, 21); + this.rbByPixels.Size = new System.Drawing.Size(103, 21); this.rbByPixels.TabIndex = 2; this.rbByPixels.TabStop = true; this.rbByPixels.Text = "rbByPixels"; @@ -211,7 +212,7 @@ private void InitializeComponent() this.rbByPercentage.FlatStyle = System.Windows.Forms.FlatStyle.System; this.rbByPercentage.Location = new System.Drawing.Point(103, 30); this.rbByPercentage.Name = "rbByPercentage"; - this.rbByPercentage.Size = new System.Drawing.Size(106, 21); + this.rbByPercentage.Size = new System.Drawing.Size(103, 21); this.rbByPercentage.TabIndex = 1; this.rbByPercentage.TabStop = true; this.rbByPercentage.Text = "rbByPercentage"; @@ -225,7 +226,7 @@ private void InitializeComponent() this.pnlWidthPercent.Location = new System.Drawing.Point(100, 54); this.pnlWidthPercent.Margin = new System.Windows.Forms.Padding(0); this.pnlWidthPercent.Name = "pnlWidthPercent"; - this.pnlWidthPercent.Size = new System.Drawing.Size(112, 21); + this.pnlWidthPercent.Size = new System.Drawing.Size(109, 21); this.pnlWidthPercent.TabIndex = 4; // // lblWidthPercent @@ -280,7 +281,7 @@ private void InitializeComponent() this.cmbScalingMode.Location = new System.Drawing.Point(100, 108); this.cmbScalingMode.Margin = new System.Windows.Forms.Padding(0); this.cmbScalingMode.Name = "cmbScalingMode"; - this.cmbScalingMode.Size = new System.Drawing.Size(224, 21); + this.cmbScalingMode.Size = new System.Drawing.Size(218, 21); this.cmbScalingMode.TabIndex = 10; // // ResizeBitmapForm @@ -291,6 +292,7 @@ private void InitializeComponent() this.Location = new System.Drawing.Point(0, 0); this.MinimumSize = new System.Drawing.Size(350, 330); this.Name = "ResizeBitmapForm"; + this.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.Text = "ResizeBitmapForm"; this.pnlSettings.ResumeLayout(false); this.tblNewSize.ResumeLayout(false); @@ -304,7 +306,6 @@ private void InitializeComponent() this.pnlWidthPercent.ResumeLayout(false); this.pnlWidthPercent.PerformLayout(); this.ResumeLayout(false); - this.PerformLayout(); } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs index dfc4afe..d900632 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.Designer.cs @@ -15,44 +15,45 @@ partial class TransformBitmapFormBase /// private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); - this.progress = new KGySoft.Drawing.ImagingTools.View.Controls.DrawingProgressStatusStrip(); + this.progress = new KGySoft.Drawing.ImagingTools.View.Controls.DrawingProgressFooter(); this.okCancelButtons = new KGySoft.Drawing.ImagingTools.View.UserControls.OkCancelButtons(); this.previewImage = new KGySoft.Drawing.ImagingTools.View.UserControls.PreviewImageControl(); - this.pnlSettings = new Controls.AutoMirrorPanel(); + this.pnlSettings = new KGySoft.Drawing.ImagingTools.View.Controls.AutoMirrorPanel(); this.SuspendLayout(); // // progress // this.progress.BackColor = System.Drawing.Color.Transparent; - this.progress.Location = new System.Drawing.Point(0, 189); + this.progress.Dock = System.Windows.Forms.DockStyle.Bottom; + this.progress.Location = new System.Drawing.Point(3, 189); this.progress.Name = "progress"; - this.progress.Size = new System.Drawing.Size(248, 22); + this.progress.Size = new System.Drawing.Size(242, 22); this.progress.TabIndex = 3; this.progress.Text = "drawingProgressStatusStrip1"; // // okCancelButtons // + this.okCancelButtons.BackColor = System.Drawing.Color.Transparent; this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; - this.okCancelButtons.Location = new System.Drawing.Point(0, 149); + this.okCancelButtons.Location = new System.Drawing.Point(3, 154); this.okCancelButtons.Name = "okCancelButtons"; - this.okCancelButtons.Size = new System.Drawing.Size(248, 35); + this.okCancelButtons.Size = new System.Drawing.Size(242, 35); this.okCancelButtons.TabIndex = 2; // // previewImage // this.previewImage.Dock = System.Windows.Forms.DockStyle.Fill; - this.previewImage.Location = new System.Drawing.Point(0, 56); + this.previewImage.Location = new System.Drawing.Point(3, 56); this.previewImage.Name = "previewImage"; - this.previewImage.Size = new System.Drawing.Size(248, 93); + this.previewImage.Size = new System.Drawing.Size(242, 98); this.previewImage.TabIndex = 1; // // pnlSettings // this.pnlSettings.Dock = System.Windows.Forms.DockStyle.Top; - this.pnlSettings.Location = new System.Drawing.Point(0, 0); + this.pnlSettings.Location = new System.Drawing.Point(3, 0); this.pnlSettings.Name = "pnlSettings"; - this.pnlSettings.Size = new System.Drawing.Size(248, 56); + this.pnlSettings.Size = new System.Drawing.Size(242, 56); this.pnlSettings.TabIndex = 0; // // TransformBitmapFormBase @@ -66,15 +67,15 @@ private void InitializeComponent() this.Controls.Add(this.progress); this.MinimizeBox = false; this.Name = "TransformBitmapFormBase"; + this.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.Text = "TransformBitmapFormBase"; this.ResumeLayout(false); - this.PerformLayout(); } #endregion - private Controls.DrawingProgressStatusStrip progress; + private Controls.DrawingProgressFooter progress; private UserControls.OkCancelButtons okCancelButtons; private UserControls.PreviewImageControl previewImage; protected Controls.AutoMirrorPanel pnlSettings; diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs index 864f2ea..69a1975 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs @@ -78,6 +78,7 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.Transparent; this.Controls.Add(this.pnlButtons); this.Name = "OkCancelButtons"; this.Size = new System.Drawing.Size(260, 35); @@ -90,7 +91,7 @@ private void InitializeComponent() #endregion private System.Windows.Forms.Button btnOK; private System.Windows.Forms.Button btnCancel; - protected System.Windows.Forms.FlowLayoutPanel pnlButtons; + private System.Windows.Forms.FlowLayoutPanel pnlButtons; private System.Windows.Forms.Button btnApply; } } diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.cs index 1b14464..10bc638 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.cs @@ -17,6 +17,7 @@ #region Usings using System.ComponentModel; +using System.Drawing; using System.Windows.Forms; #endregion From 023630f8c02317b4a9eadc23cc1de30051e00c21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 21 Jun 2021 19:15:32 +0200 Subject: [PATCH 138/211] Increasing version number --- KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/Ids.cs | 2 +- .../source.extension.vsixmanifest | 2 +- .../Properties/AssemblyInfo.cs | 6 +++--- .../Properties/AssemblyInfo.cs | 6 +++--- KGySoft.Drawing.ImagingTools/Model/InstallationInfo.cs | 2 +- KGySoft.Drawing.ImagingTools/Properties/AssemblyInfo.cs | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/Ids.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/Ids.cs index 751920c..b343c4f 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/Ids.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/Ids.cs @@ -32,7 +32,7 @@ internal static class Ids internal const string ResourceTitle = "110"; internal const string ResourceDetails = "112"; internal const int IconResourceId = 400; - internal const string Version = "2.3.0"; // Note: in .vsixmanifest it should be adjusted manually + internal const string Version = "2.4.0"; // Note: in .vsixmanifest it should be adjusted manually internal const int ExecuteImagingToolsCommandId = 0x0100; internal const int ManageDebuggerVisualizerInstallationsCommandId = 0x0101; diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/source.extension.vsixmanifest b/KGySoft.Drawing.DebuggerVisualizers.Package/source.extension.vsixmanifest index 62e6558..cfb65b8 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/source.extension.vsixmanifest +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/source.extension.vsixmanifest @@ -1,7 +1,7 @@  - + KGy SOFT Drawing DebuggerVisualizers Debugger Visualizers for System.Drawing types such as Image (Bitmap and Metafile), Icon, Graphics, BitmapData, ColorPalette and Color. https://github.com/koszeggy/KGySoft.Drawing.Tools diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/Properties/AssemblyInfo.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/Properties/AssemblyInfo.cs index 95cfbda..2d46710 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/Properties/AssemblyInfo.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/Properties/AssemblyInfo.cs @@ -35,6 +35,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.3.0.0")] -[assembly: AssemblyFileVersion("2.3.0.0")] -[assembly: AssemblyInformationalVersion("2.3.0")] +[assembly: AssemblyVersion("2.4.0")] +[assembly: AssemblyFileVersion("2.4.0")] +[assembly: AssemblyInformationalVersion("2.4.0")] diff --git a/KGySoft.Drawing.DebuggerVisualizers/Properties/AssemblyInfo.cs b/KGySoft.Drawing.DebuggerVisualizers/Properties/AssemblyInfo.cs index 9c000c0..bef6c77 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/Properties/AssemblyInfo.cs +++ b/KGySoft.Drawing.DebuggerVisualizers/Properties/AssemblyInfo.cs @@ -40,9 +40,9 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("2.3.0.0")] -[assembly: AssemblyFileVersion("2.3.0.0")] -[assembly: AssemblyInformationalVersion("2.3.0")] +[assembly: AssemblyVersion("2.4.0")] +[assembly: AssemblyFileVersion("2.4.0")] +[assembly: AssemblyInformationalVersion("2.4.0")] // Image [assembly: DebuggerVisualizer(typeof(ImageDebuggerVisualizer), typeof(ImageSerializer), diff --git a/KGySoft.Drawing.ImagingTools/Model/InstallationInfo.cs b/KGySoft.Drawing.ImagingTools/Model/InstallationInfo.cs index 3ad78d6..0ee313d 100644 --- a/KGySoft.Drawing.ImagingTools/Model/InstallationInfo.cs +++ b/KGySoft.Drawing.ImagingTools/Model/InstallationInfo.cs @@ -152,7 +152,7 @@ internal SandboxContext(string path) : base(nameof(SandboxContext), isCollectibl public string? TargetFramework { get; -#if NET40 || NET45 +#if !NET35 private set; #endif } diff --git a/KGySoft.Drawing.ImagingTools/Properties/AssemblyInfo.cs b/KGySoft.Drawing.ImagingTools/Properties/AssemblyInfo.cs index 7504347..8d3e50c 100644 --- a/KGySoft.Drawing.ImagingTools/Properties/AssemblyInfo.cs +++ b/KGySoft.Drawing.ImagingTools/Properties/AssemblyInfo.cs @@ -35,8 +35,8 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("2.3.0.0")] -[assembly: AssemblyFileVersion("2.3.0.0")] -[assembly: AssemblyInformationalVersion("2.3.0")] +[assembly: AssemblyVersion("2.4.0")] +[assembly: AssemblyFileVersion("2.4.0")] +[assembly: AssemblyInformationalVersion("2.4.0")] [assembly: NeutralResourcesLanguage("en")] From 956e0db5d4da0f6a511fe674bbfb748be0fe1e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 21 Jun 2021 19:55:16 +0200 Subject: [PATCH 139/211] Fixing resources path when using from debugger visualizer --- KGySoft.Drawing.ImagingTools/Res.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 95e8432..b8d4757 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -21,6 +21,7 @@ using System.Drawing; using System.Drawing.Imaging; using System.Globalization; +using System.IO; using System.Linq; using System.Reflection; using System.Resources; @@ -55,6 +56,8 @@ internal static class Res // ReSharper disable once CollectionNeverUpdated.Local private static readonly Cache localizableStringPropertiesCache = new Cache(GetLocalizableStringProperties); + private static string? resourcesDir; + #endregion #region Properties @@ -63,7 +66,19 @@ internal static class Res internal static CultureInfo OSLanguage { get; } internal static CultureInfo DefaultLanguage { get; } - internal static string ResourcesDir => resourceManager.ResXResourcesDir; + internal static string ResourcesDir + { + get + { + if (resourcesDir == null) + { + string path = resourceManager.ResXResourcesDir; + resourcesDir = Path.IsPathRooted(path) ? Path.GetFullPath(path) : Path.GetFullPath(Path.Combine(Files.GetExecutingPath(), path)); + } + + return resourcesDir; + } + } #endregion From 5ed24811f5fe127ce58cc6ab3849416ab8db9c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 21 Jun 2021 20:49:12 +0200 Subject: [PATCH 140/211] Download: saving as text instead of binary to adjust new lines of the current platform --- .../ViewModel/DownloadResourcesViewModel.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index 5cc5bda..db98acb 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -273,8 +273,13 @@ private void DoDownloadResources(object state) if (data == null) return; - // if there was no issue with downloading, then saving the file - File.WriteAllBytes(downloadInfo.LocalPath, data); + // If there was no issue with downloading, then saving the file. + // Using StreamReader/Writer instead of File.WriteAllBytes so the newlines are adjusted to the current platform + using var reader = new StreamReader(new MemoryStream(data), Encoding.UTF8); + using var writer = File.CreateText(downloadInfo.LocalPath); + while (!reader.EndOfStream) + writer.WriteLine(reader.ReadLine()); + downloadedCultures.Add(downloadInfo.Info); IncrementProgress(); downloaded += 1; From 55b202ea36c7cad95da89e087e814d10e8021bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 21 Jun 2021 21:19:56 +0200 Subject: [PATCH 141/211] Download: creating possible not existing resources directory --- .../ViewModel/DownloadResourcesViewModel.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index db98acb..995c8ef 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -255,6 +255,10 @@ private void DoDownloadResources(object state) // x3: 2 for download (retrieving response + downloading content), 1 for saving the file Progress = (task.Files.Count * 3, 0); int downloaded = 0; + + if (!Directory.Exists(Res.ResourcesDir)) + Directory.CreateDirectory(Res.ResourcesDir); + foreach (DownloadInfo downloadInfo in task.Files) { current = downloadInfo.FileName; From f2eded387802114929b210f3800c0aef9367a37c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 22 Jun 2021 15:31:27 +0200 Subject: [PATCH 142/211] Removing unnecessary resource --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 5 +--- KGySoft.Drawing.ImagingTools/Res.cs | 7 ++--- .../View/Forms/AppMainForm.cs | 26 +++++++------------ 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 27f354f..6c05021 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -120,11 +120,8 @@ Internal Error: {0} - - KGy SOFT Imaging Tools v{0} - - {0} [{1}{2}] – {3} + KGy SOFT Imaging Tools v{0} [{1}{2}] – {3} No Image diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index b8d4757..c1fb918 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -355,11 +355,8 @@ internal static void ApplyStringResources(object target, string name) #region Title Captions - /// KGy SOFT Imaging Tools v{0} - internal static string TitleAppNameAndVersion(Version version) => Get("Title_AppNameAndVersionFormat", version); - - /// {0} [{1}{2}] – {3} - internal static string TitleAppNameWithFileName(string title, string fileName, string modifiedMark, string caption) => Get("Title_AppNameWithFileNameFormat", title, fileName, modifiedMark, caption); + /// KGy SOFT Imaging Tools v{0} [{1}{2}] – {3} + internal static string TitleAppNameWithFileName(Version version, string fileName, string modifiedMark, string caption) => Get("Title_AppNameWithFileNameFormat", version, fileName, modifiedMark, caption); /// Type: {0} internal static string TitleType(string type) => Get("Title_TypeFormat", type); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs index bf5f239..73e59e4 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.cs @@ -29,12 +29,6 @@ namespace KGySoft.Drawing.ImagingTools.View.Forms { internal partial class AppMainForm : ImageVisualizerForm { - #region Fields - - private string? title; - - #endregion - #region Properties #region Public Properties @@ -84,20 +78,18 @@ private AppMainForm() : this(null!) #region Protected Methods - protected override void ApplyStringResources() - { - base.ApplyStringResources(); - title = Res.TitleAppNameAndVersion(typeof(Res).Assembly.GetName().Version!); - if (CommandBindings.Count > 0) - Text = ViewModel.TitleCaption; - } - protected override void ApplyViewModel() { InitPropertyBindings(); base.ApplyViewModel(); } + protected override void ApplyStringResources() + { + base.ApplyStringResources(); + Text = ViewModel.TitleCaption; + } + protected override void OnFormClosing(FormClosingEventArgs e) { if (e.CloseReason == CloseReason.UserClosing && ViewModel.IsModified) @@ -125,15 +117,17 @@ private void InitPropertyBindings() { // Base updates Text when ViewModel.TitleCaption changes. // Here adding an update also for FileName and IsModified changes in a compatible way - CommandBindings.AddPropertyChangedHandler(() => Text = ViewModel.TitleCaption, ViewModel, + CommandBindings.AddPropertyChangedHandler(() => Text = ViewModel.TitleCaption!, ViewModel, nameof(ViewModel.FileName), nameof(ViewModel.IsModified)); } private string FormatText(string? value) { + if (value == null) + return String.Empty; string? fileName = ViewModel.FileName; string name = fileName == null ? Res.TextUnnamed : Path.GetFileName(fileName); - return String.IsNullOrEmpty(value) ? title! : Res.TitleAppNameWithFileName(title!, name, ViewModel.IsModified ? "*" : String.Empty, value!); + return Res.TitleAppNameWithFileName(InstallationManager.ImagingToolsVersion, name, ViewModel.IsModified ? "*" : String.Empty, value); } #endregion From 0889d8e59cda9e8afb72b94705e5bf9941d82611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 22 Jun 2021 16:22:32 +0200 Subject: [PATCH 143/211] Configuration is applied in each view model creation so always the last settings are used, even from the VS extension --- KGySoft.Drawing.ImagingTools/Res.cs | 8 -------- .../ViewModel/ViewModelBase.cs | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index c1fb918..52a8b9f 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -294,14 +294,6 @@ static Res() ? CultureInfo.GetCultureInfo(attr.CultureName) : CultureInfo.InvariantCulture).GetClosestNeutralCulture(); DrawingModule.Initialize(); - - bool allowResXResources = Configuration.AllowResXResources; - CultureInfo desiredDisplayLanguage = allowResXResources - ? Configuration.UseOSLanguage ? OSLanguage : Configuration.DisplayLanguage // here, allowing specific languages, too - : DefaultLanguage; - - LanguageSettings.DisplayLanguage = Equals(desiredDisplayLanguage, CultureInfo.InvariantCulture) ? DefaultLanguage : desiredDisplayLanguage; - LanguageSettings.DynamicResourceManagersSource = allowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; } #endregion diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs index fdb055b..023a4d4 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs @@ -17,8 +17,10 @@ #region Usings using System; +using System.Globalization; using KGySoft.ComponentModel; +using KGySoft.Resources; #endregion @@ -44,7 +46,20 @@ internal abstract class ViewModelBase : ObservableObjectBase, IViewModel #region Constructors - protected ViewModelBase() => LanguageSettings.DisplayLanguageChanged += LanguageSettings_DisplayLanguageChanged; + protected ViewModelBase() + { + LanguageSettings.DisplayLanguageChanged += LanguageSettings_DisplayLanguageChanged; + + // Applying the configuration settings in each VM might seem to be an overkill when executed as a regular application but by using the factories from a + // consumer code any VM can be created in any order, in any thread. The VS extension creates the default VM always in a new thread, for example. + bool allowResXResources = Configuration.AllowResXResources; + CultureInfo desiredDisplayLanguage = allowResXResources + ? Configuration.UseOSLanguage ? Res.OSLanguage : Configuration.DisplayLanguage // here, allowing specific languages, too + : Res.DefaultLanguage; + + LanguageSettings.DisplayLanguage = Equals(desiredDisplayLanguage, CultureInfo.InvariantCulture) ? Res.DefaultLanguage : desiredDisplayLanguage; + LanguageSettings.DynamicResourceManagersSource = allowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; + } #endregion From c68e3c488b94e94c2c1c0d1d639f394340eec935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 22 Jun 2021 20:46:12 +0200 Subject: [PATCH 144/211] Fixing behavior and high DPI appearance on Mono --- .../View/Controls/ImageViewer.cs | 2 +- .../View/Controls/PalettePanel.cs | 2 +- .../View/Controls/ProgressFooter.cs | 10 ++ .../View/Forms/AdjustColorsFormBase.cs | 18 +++ .../View/Forms/LanguageSettingsForm.cs | 15 +++ .../View/Forms/MvvmBaseForm.cs | 9 +- .../Forms/PaletteVisualizerForm.Designer.cs | 2 +- .../View/Forms/PaletteVisualizerForm.cs | 15 +++ .../View/Forms/ResizeBitmapForm.Designer.cs | 2 - .../View/Forms/ResizeBitmapForm.cs | 11 ++ .../View/Forms/ResizeBitmapForm.resx | 120 ------------------ .../View/Forms/TransformBitmapFormBase.cs | 1 + .../UserControls/ColorVisualizerControl.cs | 17 ++- .../UserControls/OkCancelButtons.Designer.cs | 9 +- .../View/UserControls/OkCancelButtons.cs | 23 +++- .../ViewModel/ManageInstallationsViewModel.cs | 4 +- 16 files changed, 122 insertions(+), 138 deletions(-) delete mode 100644 KGySoft.Drawing.ImagingTools/View/Forms/ResizeBitmapForm.resx diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs index 1c7346a..acd0afd 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs @@ -172,7 +172,7 @@ public ImageViewer() InitializeComponent(); SetStyle(ControlStyles.Selectable | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); - scrollbarSize = new Size(SystemInformation.VerticalScrollBarWidth, SystemInformation.HorizontalScrollBarHeight); + scrollbarSize = OSUtils.IsMono ? new Size(16, 16).Scale(this.GetScale()) : new Size(SystemInformation.VerticalScrollBarWidth, SystemInformation.HorizontalScrollBarHeight); sbVertical.Width = scrollbarSize.Width; sbHorizontal.Height = scrollbarSize.Height; diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs index 6cd5ce7..dc1a906 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs @@ -167,7 +167,7 @@ public PalettePanel() DoubleBuffered = true; SetStyle(ControlStyles.Selectable, true); - scrollbarWidth = SystemInformation.VerticalScrollBarWidth; + scrollbarWidth = OSUtils.IsMono ? this.ScaleWidth(16) : SystemInformation.VerticalScrollBarWidth; sbPalette.Width = scrollbarWidth; } diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs index 804463e..c92d4b6 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs @@ -143,6 +143,16 @@ protected ProgressFooter() #region Protected Methods + protected override void OnSizeChanged(EventArgs e) + { + base.OnSizeChanged(e); + + // Fixing high DPI appearance on Mono + PointF scale; + if (OSUtils.IsMono && (scale = this.GetScale()) != new PointF(1f, 1f)) + Height = (int)(22 * scale.Y); + } + protected override void Dispose(bool disposing) { if (disposing) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.cs index 0e7f5a8..a7b6862 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AdjustColorsFormBase.cs @@ -16,6 +16,8 @@ #region Usings +using System; +using System.Drawing; using KGySoft.CoreLibraries; using KGySoft.Drawing.ImagingTools.ViewModel; @@ -59,6 +61,20 @@ private AdjustColorsFormBase() : this(null!) #region Protected Methods + protected override void OnLoad(EventArgs e) + { + // Fixing high DPI appearance on Mono + PointF scale; + if (OSUtils.IsMono && (scale = this.GetScale()) != new PointF(1f, 1f)) + { + pnlCheckBoxes.Height = (int)(25 * scale.Y); + btnReset.Width = (int)(64 * scale.X); + lblValue.Width = (int)(35 * scale.X); + } + + base.OnLoad(e); + } + protected override void ApplyResources() { Icon = Properties.Resources.Colors; @@ -67,6 +83,8 @@ protected override void ApplyResources() protected override void ApplyViewModel() { + if (OSUtils.IsMono) + pnlCheckBoxes.Height = this.ScaleHeight(25); InitCommandBindings(); InitPropertyBindings(); base.ApplyViewModel(); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs index cf38a8c..f69cf51 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/LanguageSettingsForm.cs @@ -16,6 +16,8 @@ #region Usings +using System; +using System.Drawing; using System.Globalization; using System.Windows.Forms; @@ -69,6 +71,19 @@ private static void OnFormatCultureCommand(ICommandSource - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs index 13607c3..95885b1 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/TransformBitmapFormBase.cs @@ -16,6 +16,7 @@ #region Usings +using System; using System.Windows.Forms; using KGySoft.Drawing.ImagingTools.ViewModel; diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs index 50c967e..2897281 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs @@ -184,8 +184,8 @@ public ColorVisualizerControl() #region Static Methods - private static string GetKnownColor(Color color) => KnownColors.GetValueOrDefault(color.ToArgb(), "-"); - private static string GetSystemColors(Color color) => SystemColors.GetValueOrDefault(color.ToArgb(), "-"); + private static string GetKnownColor(Color color) => KnownColors.GetValueOrDefault(color.ToArgb(), "–"); + private static string GetSystemColors(Color color) => SystemColors.GetValueOrDefault(color.ToArgb(), "–"); #endregion @@ -193,6 +193,19 @@ public ColorVisualizerControl() #region Protected Methods + protected override void OnLoad(EventArgs e) + { + // Fixing high DPI appearance on Mono + PointF scale; + if (OSUtils.IsMono && (scale = this.GetScale()) != new PointF(1f, 1f)) + { + tblColor.ColumnStyles[0].Width = (int)(50 * scale.X); + tblColor.ColumnStyles[1].Width = (int)(80 * scale.X); + } + + base.OnLoad(e); + } + protected override void Dispose(bool disposing) { if (disposing && (components != null)) diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs index 69a1975..b59080c 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.Designer.cs @@ -39,19 +39,21 @@ private void InitializeComponent() // this.btnApply.Anchor = System.Windows.Forms.AnchorStyles.None; this.btnApply.AutoSize = true; + this.btnApply.BackColor = System.Drawing.SystemColors.Control; this.btnApply.FlatStyle = System.Windows.Forms.FlatStyle.System; this.btnApply.Location = new System.Drawing.Point(176, 6); this.btnApply.Name = "btnApply"; this.btnApply.Size = new System.Drawing.Size(75, 23); this.btnApply.TabIndex = 2; this.btnApply.Text = "btnApply"; - this.btnApply.UseVisualStyleBackColor = true; + this.btnApply.UseVisualStyleBackColor = false; this.btnApply.Visible = false; // // btnCancel // this.btnCancel.Anchor = System.Windows.Forms.AnchorStyles.None; this.btnCancel.AutoSize = true; + this.btnCancel.BackColor = System.Drawing.SystemColors.Control; this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; this.btnCancel.FlatStyle = System.Windows.Forms.FlatStyle.System; this.btnCancel.Location = new System.Drawing.Point(95, 6); @@ -59,12 +61,13 @@ private void InitializeComponent() this.btnCancel.Size = new System.Drawing.Size(75, 23); this.btnCancel.TabIndex = 1; this.btnCancel.Text = "btnCancel"; - this.btnCancel.UseVisualStyleBackColor = true; + this.btnCancel.UseVisualStyleBackColor = false; // // btnOK // this.btnOK.Anchor = System.Windows.Forms.AnchorStyles.None; this.btnOK.AutoSize = true; + this.btnOK.BackColor = System.Drawing.SystemColors.Control; this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK; this.btnOK.FlatStyle = System.Windows.Forms.FlatStyle.System; this.btnOK.Location = new System.Drawing.Point(14, 6); @@ -72,7 +75,7 @@ private void InitializeComponent() this.btnOK.Size = new System.Drawing.Size(75, 23); this.btnOK.TabIndex = 0; this.btnOK.Text = "btnOK"; - this.btnOK.UseVisualStyleBackColor = true; + this.btnOK.UseVisualStyleBackColor = false; // // OkCancelButtons // diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.cs index 10bc638..da890df 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/OkCancelButtons.cs @@ -16,6 +16,7 @@ #region Usings +using System; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; @@ -62,16 +63,28 @@ public bool ApplyButtonVisible #region Constructors - public OkCancelButtons() - { - AutoScaleMode = AutoScaleMode.Inherit; - InitializeComponent(); - } + public OkCancelButtons() => InitializeComponent(); #endregion #region Methods + protected override void OnLoad(EventArgs e) + { + // Fixing high DPI appearance on Mono + PointF scale; + if (OSUtils.IsMono && (scale = this.GetScale()) != new PointF(1f, 1f)) + { + Height = (int)(35 * scale.Y); + var referenceButtonSize = new Size(75, 23); + OKButton.Size = referenceButtonSize.Scale(scale); + CancelButton.Size = referenceButtonSize.Scale(scale); + ApplyButton.Size = referenceButtonSize.Scale(scale); + } + + base.OnLoad(e); + } + protected override void Dispose(bool disposing) { if (disposing) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs index c6c7c89..d54083f 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs @@ -53,8 +53,8 @@ internal class ManageInstallationsViewModel : ViewModelBase internal IList> Installations { get => Get>>(); set => Set(value); } internal string SelectedInstallation { get => Get(String.Empty); set => Set(value); } internal string CurrentPath { get => Get(String.Empty); set => Set(value); } - internal string StatusText { get => Get("-"); set => Set(value); } - internal string AvailableVersionText { get => Get("-"); set => Set(value); } + internal string StatusText { get => Get("–"); set => Set(value); } + internal string AvailableVersionText { get => Get("–"); set => Set(value); } internal Func? SelectFolderCallback { get => Get?>(); set => Set(value); } From 5e9ea78fb05cf03aca413d7a99859f2a0a441b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 23 Jun 2021 12:34:47 +0200 Subject: [PATCH 145/211] Manage installations: adapting other forms' button layout and behavior --- .../Forms/ManageInstallationsForm.Designer.cs | 93 +++++++++---------- .../View/Forms/ManageInstallationsForm.cs | 17 ++++ 2 files changed, 63 insertions(+), 47 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs index 10e423e..8cd9513 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.Designer.cs @@ -18,12 +18,12 @@ partial class ManageInstallationsForm private void InitializeComponent() { this.gbInstallation = new System.Windows.Forms.GroupBox(); - this.tblButtons = new System.Windows.Forms.TableLayoutPanel(); - this.btnRemove = new System.Windows.Forms.Button(); - this.btnInstall = new System.Windows.Forms.Button(); this.pnlStatus = new KGySoft.Drawing.ImagingTools.View.Controls.AutoMirrorPanel(); this.lblStatusText = new System.Windows.Forms.Label(); this.lblStatus = new System.Windows.Forms.Label(); + this.pnlButtons = new System.Windows.Forms.FlowLayoutPanel(); + this.btnRemove = new System.Windows.Forms.Button(); + this.btnInstall = new System.Windows.Forms.Button(); this.tbPath = new System.Windows.Forms.TextBox(); this.lblPath = new System.Windows.Forms.Label(); this.gbVisualStudioVersions = new System.Windows.Forms.GroupBox(); @@ -31,8 +31,8 @@ private void InitializeComponent() this.gbAvailableVersion = new System.Windows.Forms.GroupBox(); this.lblAvailableVersion = new System.Windows.Forms.Label(); this.gbInstallation.SuspendLayout(); - this.tblButtons.SuspendLayout(); this.pnlStatus.SuspendLayout(); + this.pnlButtons.SuspendLayout(); this.gbVisualStudioVersions.SuspendLayout(); this.gbAvailableVersion.SuspendLayout(); this.SuspendLayout(); @@ -40,7 +40,7 @@ private void InitializeComponent() // gbInstallation // this.gbInstallation.Controls.Add(this.pnlStatus); - this.gbInstallation.Controls.Add(this.tblButtons); + this.gbInstallation.Controls.Add(this.pnlButtons); this.gbInstallation.Controls.Add(this.tbPath); this.gbInstallation.Controls.Add(this.lblPath); this.gbInstallation.Dock = System.Windows.Forms.DockStyle.Fill; @@ -51,44 +51,6 @@ private void InitializeComponent() this.gbInstallation.TabStop = false; this.gbInstallation.Text = "gbInstallation"; // - // tblButtons - // - this.tblButtons.ColumnCount = 2; - this.tblButtons.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tblButtons.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tblButtons.Controls.Add(this.btnRemove, 1, 0); - this.tblButtons.Controls.Add(this.btnInstall, 0, 0); - this.tblButtons.Dock = System.Windows.Forms.DockStyle.Bottom; - this.tblButtons.Location = new System.Drawing.Point(3, 79); - this.tblButtons.Name = "tblButtons"; - this.tblButtons.RowCount = 1; - this.tblButtons.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tblButtons.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 37F)); - this.tblButtons.Size = new System.Drawing.Size(372, 37); - this.tblButtons.TabIndex = 3; - // - // btnRemove - // - this.btnRemove.Anchor = System.Windows.Forms.AnchorStyles.None; - this.btnRemove.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.btnRemove.Location = new System.Drawing.Point(241, 7); - this.btnRemove.Name = "btnRemove"; - this.btnRemove.Size = new System.Drawing.Size(75, 23); - this.btnRemove.TabIndex = 1; - this.btnRemove.Text = "btnRemove"; - this.btnRemove.UseVisualStyleBackColor = true; - // - // btnInstall - // - this.btnInstall.Anchor = System.Windows.Forms.AnchorStyles.None; - this.btnInstall.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.btnInstall.Location = new System.Drawing.Point(55, 7); - this.btnInstall.Name = "btnInstall"; - this.btnInstall.Size = new System.Drawing.Size(75, 23); - this.btnInstall.TabIndex = 0; - this.btnInstall.Text = "btnInstall"; - this.btnInstall.UseVisualStyleBackColor = true; - // // pnlStatus // this.pnlStatus.Controls.Add(this.lblStatusText); @@ -97,7 +59,7 @@ private void InitializeComponent() this.pnlStatus.Location = new System.Drawing.Point(3, 49); this.pnlStatus.Name = "pnlStatus"; this.pnlStatus.Padding = new System.Windows.Forms.Padding(0, 3, 0, 0); - this.pnlStatus.Size = new System.Drawing.Size(372, 30); + this.pnlStatus.Size = new System.Drawing.Size(372, 32); this.pnlStatus.TabIndex = 2; // // lblStatusText @@ -107,7 +69,7 @@ private void InitializeComponent() this.lblStatusText.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.lblStatusText.Location = new System.Drawing.Point(47, 3); this.lblStatusText.Name = "lblStatusText"; - this.lblStatusText.Size = new System.Drawing.Size(325, 27); + this.lblStatusText.Size = new System.Drawing.Size(325, 29); this.lblStatusText.TabIndex = 1; this.lblStatusText.Text = "lblStatusText"; // @@ -122,6 +84,42 @@ private void InitializeComponent() this.lblStatus.TabIndex = 0; this.lblStatus.Text = "lblStatus"; // + // pnlButtons + // + this.pnlButtons.Controls.Add(this.btnRemove); + this.pnlButtons.Controls.Add(this.btnInstall); + this.pnlButtons.Dock = System.Windows.Forms.DockStyle.Bottom; + this.pnlButtons.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft; + this.pnlButtons.Location = new System.Drawing.Point(3, 81); + this.pnlButtons.Name = "pnlButtons"; + this.pnlButtons.Padding = new System.Windows.Forms.Padding(3); + this.pnlButtons.Size = new System.Drawing.Size(372, 35); + this.pnlButtons.TabIndex = 3; + // + // btnRemove + // + this.btnRemove.Anchor = System.Windows.Forms.AnchorStyles.None; + this.btnRemove.AutoSize = true; + this.btnRemove.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.btnRemove.Location = new System.Drawing.Point(287, 6); + this.btnRemove.Name = "btnRemove"; + this.btnRemove.Size = new System.Drawing.Size(76, 23); + this.btnRemove.TabIndex = 1; + this.btnRemove.Text = "btnRemove"; + this.btnRemove.UseVisualStyleBackColor = true; + // + // btnInstall + // + this.btnInstall.Anchor = System.Windows.Forms.AnchorStyles.None; + this.btnInstall.AutoSize = true; + this.btnInstall.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.btnInstall.Location = new System.Drawing.Point(206, 6); + this.btnInstall.Name = "btnInstall"; + this.btnInstall.Size = new System.Drawing.Size(75, 23); + this.btnInstall.TabIndex = 0; + this.btnInstall.Text = "btnInstall"; + this.btnInstall.UseVisualStyleBackColor = true; + // // tbPath // this.tbPath.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend; @@ -202,9 +200,10 @@ private void InitializeComponent() this.Text = "ManageInstallationsForm"; this.gbInstallation.ResumeLayout(false); this.gbInstallation.PerformLayout(); - this.tblButtons.ResumeLayout(false); this.pnlStatus.ResumeLayout(false); this.pnlStatus.PerformLayout(); + this.pnlButtons.ResumeLayout(false); + this.pnlButtons.PerformLayout(); this.gbVisualStudioVersions.ResumeLayout(false); this.gbAvailableVersion.ResumeLayout(false); this.ResumeLayout(false); @@ -213,7 +212,7 @@ private void InitializeComponent() #endregion private System.Windows.Forms.GroupBox gbInstallation; - private System.Windows.Forms.TableLayoutPanel tblButtons; + private System.Windows.Forms.FlowLayoutPanel pnlButtons; private System.Windows.Forms.Button btnRemove; private System.Windows.Forms.Button btnInstall; private Controls.AutoMirrorPanel pnlStatus; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs index 05b0985..8ffb992 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs @@ -16,7 +16,9 @@ #region Usings +using System; using System.Collections.Generic; +using System.Drawing; using KGySoft.Drawing.ImagingTools.ViewModel; @@ -55,6 +57,21 @@ private ManageInstallationsForm() : this(null!) #region Protected Methods + protected override void OnLoad(EventArgs e) + { + // Fixing high DPI appearance on Mono + PointF scale; + if (OSUtils.IsMono && (scale = this.GetScale()) != new PointF(1f, 1f)) + { + pnlButtons.Height = (int)(35 * scale.Y); + var referenceButtonSize = new Size(75, 23); + btnInstall.Size = referenceButtonSize.Scale(scale); + btnRemove.Size = referenceButtonSize.Scale(scale); + } + + base.OnLoad(e); + } + protected override void ApplyResources() { base.ApplyResources(); From b0b2291e388474964350cf85dfa1146893f27b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 23 Jun 2021 12:46:30 +0200 Subject: [PATCH 146/211] Adjusting resource texts --- .../KGySoft.Drawing.ImagingTools.Messages.resx | 6 +++--- KGySoft.Drawing.ImagingTools/Res.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 6c05021..a88e1ad 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -159,8 +159,8 @@ Size: {0}x{1} - - Palette Count: {0} + + Palette Color Count: {0} Visible Clip Bounds: {{X = {0}, Y = {1}, Size = {2}x{3}}} @@ -277,7 +277,7 @@ Pixel Format: {3} Unknown format: {0} - Palette count: {0} + Palette color count: {0} Images: {0} diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 52a8b9f..0753416 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -356,8 +356,8 @@ internal static void ApplyStringResources(object target, string name) /// Size: {0}x{1} internal static string TitleSize(Size size) => Get("Title_SizeFormat", size.Width, size.Height); - /// Palette Count: {0} - internal static string TitlePaletteCount(int count) => Get("Title_PaletteCountFormat", count); + /// Palette Color Count: {0} + internal static string TitlePaletteCount(int count) => Get("Title_ColorCountFormat", count); /// Visible Clip Bounds: {{X = {0}, Y = {1}, Size = {2}x{3}}} internal static string TitleVisibleClip(Rectangle rect) => Get("Title_VisibleClipFormat", rect.X, rect.Y, rect.Width, rect.Height); @@ -411,7 +411,7 @@ internal static string InfoBitmapData(Size size, int stride, PixelFormat pixelFo /// Unknown format: {0} internal static string InfoUnknownFormat(Guid format) => Get("InfoText_UnknownFormat", format); - /// Palette count: {0} + /// Palette color count: {0} internal static string InfoPalette(int count) => Get("InfoText_PaletteFormat", count); /// Images: {0} From 3654060149fad6e09e185a43f81fea0ab2efad0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 23 Jun 2021 13:49:29 +0200 Subject: [PATCH 147/211] Fixing the build for all platfor targets --- ...ft.Drawing.DebuggerVisualizers.Test.csproj | 4 +-- ...KGySoft.Drawing.DebuggerVisualizers.csproj | 4 +-- .../KGySoft.Drawing.ImagingTools.csproj | 4 +-- .../View/Controls/AdvancedDataGridView.cs | 2 +- .../_Classes/Configuration.cs | 3 +- .../_Extensions/StringExtensions.cs | 30 +++++++++++++++---- 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj b/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj index a285c34..e2d13b7 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj @@ -1,8 +1,8 @@  - - net45 + net35;net40;net45;netcoreapp3.0;net5.0-windows + false KGySoft.Drawing.DebuggerVisualizers.Test true diff --git a/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj b/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj index da50329..8d2472e 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj @@ -1,8 +1,8 @@  - - net45 + net35;net40;net45;netcoreapp3.0;net5.0-windows + false KGySoft.Drawing.DebuggerVisualizers bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj index c2caeb4..d47ac4a 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj @@ -1,8 +1,8 @@  - - net45 + net35;net40;net45;netcoreapp3.0;net5.0-windows + false KGySoft.Drawing.ImagingTools bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs index 229060a..159cd1b 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs @@ -33,7 +33,7 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls { #region Usings -#if NET35 +#if NET35 || NET40 using ValidationList = IList; #else using ValidationList = IReadOnlyList; diff --git a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs index 74c1f94..aeaa059 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs @@ -105,7 +105,8 @@ static Configuration() #if NET35 // To prevent serializing CultureInfo by DisplayName instead of Name typeof(CultureInfo).RegisterTypeConverter(); - +#endif +#if NET35 || NET40 // To be able to use HTTP requests with TLS 1.2 security protocol (may not work on Windows XP) ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072; #elif NETFRAMEWORK diff --git a/KGySoft.Drawing.ImagingTools/_Extensions/StringExtensions.cs b/KGySoft.Drawing.ImagingTools/_Extensions/StringExtensions.cs index af0ebff..9abc90f 100644 --- a/KGySoft.Drawing.ImagingTools/_Extensions/StringExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/_Extensions/StringExtensions.cs @@ -1,14 +1,32 @@ -using System; -using System.Collections.Generic; +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: StringExtensions.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + using System.Globalization; -using System.Linq; using System.Text; -using System.Threading.Tasks; + +#endregion namespace KGySoft.Drawing.ImagingTools { internal static class StringExtensions { + #region Methods + /// /// Removes accents from strings for better chances to match a filter pattern when searching. /// @@ -28,5 +46,7 @@ internal static string StripAccents(this string s) // If it had to be displayed, then a recombining normalization would also be necessary. return stripped.ToString(); } + + #endregion } -} +} \ No newline at end of file From 06adbd9041bf3e07c1f3c162a51b306010c9c911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 23 Jun 2021 17:56:19 +0200 Subject: [PATCH 148/211] Handling language change from different threads --- .../View/Forms/MvvmBaseForm.cs | 15 +++++---- .../ViewModel/EditResourcesViewModel.cs | 7 +++++ .../ViewModel/LanguageSettingsViewModel.cs | 15 +++------ .../ViewModel/ViewModelBase.cs | 31 ++++++++++++++++--- 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index d115ee5..1720f9a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -43,7 +43,6 @@ internal partial class MvvmBaseForm : BaseForm, IView private ErrorProvider? errorProvider; private ICommand? validationResultsChangesCommand; - private bool isClosing; private bool isLoaded; private bool isRtlChanging; private Point location; @@ -179,8 +178,6 @@ protected override void OnFormClosing(FormClosingEventArgs e) location = Location; } - if (!e.Cancel) - isClosing = true; base.OnFormClosing(e); } @@ -211,8 +208,8 @@ private void InitPropertyBindings() private void InitCommandBindings() { - CommandBindings.Add(OnDisplayLanguageChangedCommand) - .AddSource(typeof(LanguageSettings), nameof(LanguageSettings.DisplayLanguageChanged)); + CommandBindings.Add(OnDisplayLanguageChangedGlobalCommand) + .AddSource(typeof(LanguageSettings), nameof(LanguageSettings.DisplayLanguageChangedGlobal)); } private ErrorProvider CreateProvider(ValidationSeverity level) => new ErrorProvider(components) @@ -231,7 +228,7 @@ private void InitCommandBindings() private void InvokeIfRequired(Action action) { - if (isClosing || Disposing || IsDisposed) + if (Disposing || IsDisposed) return; try @@ -270,11 +267,11 @@ private void ApplyRightToLeft() #region Command Handlers - private void OnDisplayLanguageChangedCommand() + private void OnDisplayLanguageChangedGlobalCommand() => InvokeIfRequired(() => { ApplyRightToLeft(); ApplyStringResources(); - } + }); private void OnValidationResultsChangedCommand(ValidationResultsCollection? validationResults) { @@ -294,6 +291,8 @@ private void OnValidationResultsChangedCommand(ValidationResultsCollection? vali #region Explicit Interface Implementations + void IDisposable.Dispose() => InvokeIfRequired(Dispose); + void IView.ShowDialog(IntPtr ownerHandle) { do diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index 603bae5..79f9644 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -88,6 +88,8 @@ internal class EditResourcesViewModel : ViewModelBase internal ICommandState ApplyResourcesCommandState => Get(() => new CommandState()); + internal Action? SaveConfigurationCallback { get; set; } + #endregion #region Constructors @@ -144,6 +146,7 @@ protected override void Dispose(bool disposing) (FilteredSet as IDisposable)?.Dispose(); } + SaveConfigurationCallback = null; base.Dispose(disposing); } @@ -333,6 +336,10 @@ private bool TrySaveResources() private void ApplyResources() { + // As a first step, we save the configuration explicitly. + // Otherwise, it would be possible to select a language without applying it, then editing the resources and applying the new language here, + // in which case the configuration may remain unsaved. + SaveConfigurationCallback?.Invoke(); LanguageSettings.DynamicResourceManagersSource = ResourceManagerSources.CompiledAndResX; if (Equals(LanguageSettings.DisplayLanguage, culture)) ResHelper.RaiseLanguageChanged(); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index 3e61571..4c8bad1 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -149,15 +149,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) } } - protected override void ApplyDisplayLanguage() - { - // We are saving configuration when new display language has been applied. - // We do this indirectly because we have to save the resources even if the new language is applied from editing resources. - // Otherwise, it would be possible to select a language without applying it, then editing the resources and applying the new language there, - // in which case the configuration may remain unsaved. - SaveConfiguration(); - UpdateApplyCommandState(); - } + protected override void ApplyDisplayLanguage() => UpdateApplyCommandState(); protected override void Dispose(bool disposing) { @@ -213,11 +205,12 @@ private void ApplyAndSave() if (!IsModified) return; + SaveConfiguration(); + // Applying the current language CultureInfo currentLanguage = CurrentLanguage; LanguageSettings.DynamicResourceManagersSource = AllowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; - // This will trigger SaveConfiguration, too if (Equals(LanguageSettings.DisplayLanguage, currentLanguage)) ResHelper.RaiseLanguageChanged(); else @@ -257,6 +250,8 @@ private void SaveConfiguration() private void OnEditResourcesCommand() { using IViewModel viewModel = ViewModelFactory.CreateEditResources(CurrentLanguage); + if (viewModel is EditResourcesViewModel vm) + vm.SaveConfigurationCallback = SaveConfiguration; ShowChildViewCallback?.Invoke(viewModel); availableResXLanguages = null; selectableLanguages = null; diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs index 023a4d4..109af08 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs @@ -18,6 +18,7 @@ using System; using System.Globalization; +using System.Threading; using KGySoft.ComponentModel; using KGySoft.Resources; @@ -48,8 +49,6 @@ internal abstract class ViewModelBase : ObservableObjectBase, IViewModel protected ViewModelBase() { - LanguageSettings.DisplayLanguageChanged += LanguageSettings_DisplayLanguageChanged; - // Applying the configuration settings in each VM might seem to be an overkill when executed as a regular application but by using the factories from a // consumer code any VM can be created in any order, in any thread. The VS extension creates the default VM always in a new thread, for example. bool allowResXResources = Configuration.AllowResXResources; @@ -59,6 +58,8 @@ protected ViewModelBase() LanguageSettings.DisplayLanguage = Equals(desiredDisplayLanguage, CultureInfo.InvariantCulture) ? Res.DefaultLanguage : desiredDisplayLanguage; LanguageSettings.DynamicResourceManagersSource = allowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; + + LanguageSettings.DisplayLanguageChangedGlobal += LanguageSettings_DisplayLanguageChangedGlobal; } #endregion @@ -102,7 +103,7 @@ protected override void Dispose(bool disposing) if (IsDisposed) return; - LanguageSettings.DisplayLanguageChanged -= LanguageSettings_DisplayLanguageChanged; + LanguageSettings.DisplayLanguageChangedGlobal -= LanguageSettings_DisplayLanguageChangedGlobal; base.Dispose(disposing); } @@ -116,7 +117,29 @@ protected override void Dispose(bool disposing) #region Event Handlers - private void LanguageSettings_DisplayLanguageChanged(object sender, EventArgs e) => ApplyDisplayLanguage(); + private void LanguageSettings_DisplayLanguageChangedGlobal(object sender, EventArgs e) + { + // As we get notification from any thread we read the current thread settings from the configuration + bool allowResXResources = Configuration.AllowResXResources; + CultureInfo desiredDisplayLanguage = allowResXResources + ? Configuration.UseOSLanguage ? Res.OSLanguage : Configuration.DisplayLanguage + : Res.DefaultLanguage; + if (Equals(desiredDisplayLanguage, CultureInfo.InvariantCulture)) + desiredDisplayLanguage = Res.DefaultLanguage; + + // The event was raised from another thread + if (!Equals(LanguageSettings.DisplayLanguage, desiredDisplayLanguage)) + { + LanguageSettings.DisplayLanguage = desiredDisplayLanguage; + + // we exit here because the line above end up calling this handler again from current thread + return; + } + + // Trying to apply the new language in the thread of the corresponding view + if (!TryInvokeSync(ApplyDisplayLanguage)) + ApplyDisplayLanguage(); + } #endregion From 3b26aeba00594c5f9395238dd4712873814a35f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 23 Jun 2021 20:49:00 +0200 Subject: [PATCH 149/211] VS Package: creating both the view model and the view on a new thread --- .../_Classes/ExecuteImagingToolsCommand.cs | 40 +---------- ...eDebuggerVisualizerInstallationsCommand.cs | 16 ++--- .../_Classes/ViewHelper.cs | 72 +++++++++++++++++++ .../View/Forms/ManageInstallationsForm.cs | 4 +- .../ViewModel/ManageInstallationsViewModel.cs | 2 +- changelog.txt | 4 +- 6 files changed, 84 insertions(+), 54 deletions(-) create mode 100644 KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ViewHelper.cs diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ExecuteImagingToolsCommand.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ExecuteImagingToolsCommand.cs index bfd9754..3f8fa26 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ExecuteImagingToolsCommand.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ExecuteImagingToolsCommand.cs @@ -18,7 +18,6 @@ using System; using System.ComponentModel.Design; -using System.Threading; using KGySoft.Drawing.ImagingTools.View; using KGySoft.Drawing.ImagingTools.ViewModel; @@ -37,8 +36,7 @@ internal static class ExecuteImagingToolsCommand private static MenuCommand? commandInstance; private static IServiceProvider? serviceProvider; - private static IViewModel? imagingToolsViewModel; - private static IView? imagingToolsView; + private static volatile IView? imagingToolsView; #endregion @@ -61,8 +59,6 @@ internal static void DestroyCommand() { imagingToolsView?.Dispose(); imagingToolsView = null; - imagingToolsViewModel?.Dispose(); - imagingToolsViewModel = null; } #endregion @@ -74,50 +70,18 @@ private static void OnExecuteImagingToolsCommand(object sender, EventArgs e) try { if (imagingToolsView == null || imagingToolsView.IsDisposed) - { - imagingToolsViewModel?.Dispose(); - imagingToolsViewModel = ViewModelFactory.CreateDefault(); - imagingToolsView = CreateViewInNewThread(imagingToolsViewModel); - } + imagingToolsView = ViewHelper.CreateViewInNewThread(ViewModelFactory.CreateDefault); else imagingToolsView.Show(); } catch (Exception ex) { imagingToolsView?.Dispose(); - imagingToolsViewModel?.Dispose(); imagingToolsView = null; ShellDialogs.Error(serviceProvider!, Res.ErrorMessageUnexpectedError(ex.Message)); } } - private static IView CreateViewInNewThread(IViewModel viewModel) - { - IView? result = null; - using var created = new ManualResetEvent(false); - - // Creating a non-background STA thread for the view so the possible lagging of VisualStudio will not affect its performance - var t = new Thread(() => - { - result = ViewFactory.CreateView(viewModel); - - // ReSharper disable once AccessToDisposedClosure - disposed only after awaited - created.Set(); - - // Now the view is shown as a dialog and this thread is kept alive until it is closed. - // The caller method returns once the view is created and the result is also stored and can - // be re-used until closing the view and thus exiting the thread. - result.ShowDialog(); - result.Dispose(); - }); - - t.SetApartmentState(ApartmentState.STA); - t.IsBackground = false; - t.Start(); - created.WaitOne(); - return result!; - } - #endregion #endregion diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ManageDebuggerVisualizerInstallationsCommand.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ManageDebuggerVisualizerInstallationsCommand.cs index 887b916..c4f67cf 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ManageDebuggerVisualizerInstallationsCommand.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ManageDebuggerVisualizerInstallationsCommand.cs @@ -38,7 +38,6 @@ internal static class ManageDebuggerVisualizerInstallationsCommand private static MenuCommand? commandInstance; private static IServiceProvider serviceProvider = default!; private static IVsShell? shellService; - private static IViewModel? manageInstallationsViewModel; private static IView? manageInstallationsView; #endregion @@ -53,7 +52,7 @@ internal static MenuCommand GetCreateCommand(IServiceProvider package, IVsShell? { serviceProvider = package; shellService = vsShell; - commandInstance = new OleMenuCommand(OnExecuteImagingToolsCommand, new CommandID(Ids.CommandSet, Ids.ManageDebuggerVisualizerInstallationsCommandId)); + commandInstance = new OleMenuCommand(OnExecuteManageDebuggerVisualizerInstallationsCommand, new CommandID(Ids.CommandSet, Ids.ManageDebuggerVisualizerInstallationsCommandId)); } return commandInstance; @@ -63,33 +62,28 @@ internal static void DestroyCommand() { manageInstallationsView?.Dispose(); manageInstallationsView = null; - manageInstallationsViewModel?.Dispose(); - manageInstallationsViewModel = null; } #endregion #region Event handlers - private static void OnExecuteImagingToolsCommand(object sender, EventArgs e) + private static void OnExecuteManageDebuggerVisualizerInstallationsCommand(object sender, EventArgs e) { try { if (manageInstallationsView == null || manageInstallationsView.IsDisposed) { object? documentsDirObj = null; - manageInstallationsViewModel?.Dispose(); shellService?.GetProperty((int)__VSSPROPID2.VSSPROPID_VisualStudioDir, out documentsDirObj); - manageInstallationsViewModel = ViewModelFactory.CreateManageInstallations(documentsDirObj?.ToString()); - manageInstallationsView = ViewFactory.CreateView(manageInstallationsViewModel); + manageInstallationsView = ViewHelper.CreateViewInNewThread(() => ViewModelFactory.CreateManageInstallations(documentsDirObj?.ToString())); } - - manageInstallationsView.Show(); + else + manageInstallationsView.Show(); } catch (Exception ex) { manageInstallationsView?.Dispose(); - manageInstallationsViewModel?.Dispose(); manageInstallationsView = null; ShellDialogs.Error(serviceProvider, Res.ErrorMessageUnexpectedError(ex.Message)); } diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ViewHelper.cs b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ViewHelper.cs new file mode 100644 index 0000000..7f5016f --- /dev/null +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/_Classes/ViewHelper.cs @@ -0,0 +1,72 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: ViewHelper.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System; +using System.Threading; + +using KGySoft.Drawing.ImagingTools.View; +using KGySoft.Drawing.ImagingTools.ViewModel; + +#endregion + +#nullable enable + +namespace KGySoft.Drawing.DebuggerVisualizers.Package +{ + internal static class ViewHelper + { + #region Methods + + /// + /// Creates the view (along with the view model) in a new STA thread. This has two benefits: + /// 1.) The possible lagging of Visual Studio will not affect the view + /// 2.) Creation of the view model might change the display language of the current thread, which would be the thread of Visual Studio in this project. + /// + /// The view model factory. + /// + internal static IView CreateViewInNewThread(Func viewModelFactory) + { + IView? result = null; + using var created = new ManualResetEvent(false); + + // Creating a non-background STA thread for the view so the possible lagging of VisualStudio will not affect its performance + var t = new Thread(() => + { + using IViewModel viewModel = viewModelFactory.Invoke(); + result = ViewFactory.CreateView(viewModel); + + // ReSharper disable once AccessToDisposedClosure - disposed only after awaited + created.Set(); + + // Now the view is shown as a dialog and this thread is kept alive until it is closed. + // The caller method returns once the view is created and the result is also stored and can + // be re-used until closing the view and thus exiting the thread. + result.ShowDialog(); + result.Dispose(); + }); + + t.SetApartmentState(ApartmentState.STA); + t.IsBackground = false; + t.Start(); + created.WaitOne(); + return result!; + } + + #endregion + } +} diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs index 8ffb992..4e3acc2 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ManageInstallationsForm.cs @@ -104,8 +104,8 @@ private void InitViewModelDependencies() private void InitPropertyBindings() { - // will not change so not as an actual binding - cmbInstallations.DataSource = ViewModel.Installations; + // VM.Installations -> cmbInstallations.DataSource + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Installations), nameof(cmbInstallations.DataSource), cmbInstallations); // VM.SelectedInstallation <-> cmbInstallations.SelectedValue CommandBindings.AddTwoWayPropertyBinding(ViewModel, nameof(ViewModel.SelectedInstallation), cmbInstallations, nameof(cmbInstallations.SelectedValue)); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs index d54083f..df8803d 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ManageInstallationsViewModel.cs @@ -99,7 +99,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) protected override void ApplyDisplayLanguage() { InitAvailableVersion(); - var currentPath = CurrentPath; + string currentPath = CurrentPath; InitInstallations(); CurrentPath = currentPath; UpdateStatus(currentPath); diff --git a/changelog.txt b/changelog.txt index bcbbe79..0ecef82 100644 --- a/changelog.txt +++ b/changelog.txt @@ -60,8 +60,8 @@ * KGySoft.Drawing.DebuggerVisualizers.Package.dll ================================================= -* When ImagingTools is executed from Visual Studio Tools menu a new thread is created so the possible lagging of - Visual Studio does not affect the performance of ImagingTools anymore. +* ImagingTools and Manage Installations installations in Visual Studio Tools menu are executed on a new thread + so the possible lagging of Visual Studio does not affect the performance anymore. ~~~~~~~~~ From c234383c3258eca3f6e2ef82d13442293975168e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 24 Jun 2021 16:20:28 +0200 Subject: [PATCH 150/211] Introducing Res.DisplayLanguage because when used from debugger visualizer, VS always overwrites CurrentUICulture --- KGySoft.Drawing.ImagingTools/Res.cs | 58 ++++++++++++++++++- .../View/Components/AdvancedToolTip.cs | 8 +-- KGySoft.Drawing.ImagingTools/View/Dialogs.cs | 2 +- .../View/Forms/MvvmBaseForm.cs | 14 +++-- .../DithererStrengthEditorControl.cs | 2 +- .../QuantizerThresholdEditorControl.cs | 2 +- .../DrawToolTipEventArgsExtensions.cs | 2 +- .../ViewModel/DownloadResourcesViewModel.cs | 4 +- .../ViewModel/EditResourcesViewModel.cs | 18 +++--- .../ViewModel/LanguageSettingsViewModel.cs | 10 ++-- .../ViewModel/ViewModelBase.cs | 39 +------------ .../_Classes/InstallationManager.cs | 2 +- .../_Classes/ResHelper.cs | 7 +-- 13 files changed, 95 insertions(+), 73 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 0753416..5de7209 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -25,6 +25,7 @@ using System.Linq; using System.Reflection; using System.Resources; +using System.Threading; using KGySoft.Collections; using KGySoft.CoreLibraries; @@ -57,6 +58,23 @@ internal static class Res private static readonly Cache localizableStringPropertiesCache = new Cache(GetLocalizableStringProperties); private static string? resourcesDir; + private static CultureInfo displayLanguage; + private static EventHandler? displayLanguageChanged; + + #endregion + + #region Events + + /// + /// Occurs when property changed. + /// Similar to , which is not quite reliable when executing as a debugger visualizer because + /// Visual Studio resets the on every keystroke and other events. + /// + internal static event EventHandler DisplayLanguageChanged + { + add => value.AddSafe(ref displayLanguageChanged); + remove => value.RemoveSafe(ref displayLanguageChanged); + } #endregion @@ -80,6 +98,32 @@ internal static string ResourcesDir } } + /// + /// Represents the current display language. Similar to and + /// but this property is not thread-bounded and can be used reliably even when running as a debugger visualizer where Visual Studio always resets + /// the property on every keystroke. + /// + internal static CultureInfo DisplayLanguage + { + get + { + CultureInfo result = displayLanguage; + Thread.CurrentThread.CurrentUICulture = result; + return result; + } + set + { + // Always setting also the current thread because when running from debugger visualizer VS may change it independently from the this code base. + // It is also needed for DynamicResourceManager instances of the different KGy SOFT libraries to save content automatically on language change. + LanguageSettings.DisplayLanguage = value; + if (Equals(displayLanguage, value)) + return; + + displayLanguage = value; + OnDisplayLanguageChanged(); + } + } + #endregion #region Title Captions @@ -294,6 +338,16 @@ static Res() ? CultureInfo.GetCultureInfo(attr.CultureName) : CultureInfo.InvariantCulture).GetClosestNeutralCulture(); DrawingModule.Initialize(); + + bool allowResXResources = Configuration.AllowResXResources; + displayLanguage = allowResXResources + ? Configuration.UseOSLanguage ? OSLanguage : Configuration.DisplayLanguage // here, allowing specific languages, too + : DefaultLanguage; + + if (Equals(displayLanguage, CultureInfo.InvariantCulture)) + displayLanguage = DefaultLanguage; + DisplayLanguage = displayLanguage; + LanguageSettings.DynamicResourceManagersSource = allowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; } #endregion @@ -311,7 +365,9 @@ internal static void EnsureInitialized() { } - internal static string? GetStringOrNull(string id) => resourceManager.GetString(id, LanguageSettings.DisplayLanguage); + internal static void OnDisplayLanguageChanged() => displayLanguageChanged?.Invoke(null, EventArgs.Empty); + + internal static string? GetStringOrNull(string id) => resourceManager.GetString(id, DisplayLanguage); internal static string Get(string id) => GetStringOrNull(id) ?? String.Format(CultureInfo.InvariantCulture, unavailableResource, id); diff --git a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs index 9346136..5f02a0d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs @@ -53,7 +53,7 @@ protected override void Dispose(bool disposing) if (disposing) { Draw -= AdvancedToolTip_Draw; - LanguageSettings.DisplayLanguageChanged -= LanguageSettings_DisplayLanguageChanged; + Res.DisplayLanguageChanged -= Res_DisplayLanguageChanged; } base.Dispose(disposing); @@ -65,18 +65,18 @@ protected override void Dispose(bool disposing) private void Initialize() { - LanguageSettings.DisplayLanguageChanged += LanguageSettings_DisplayLanguageChanged; + Res.DisplayLanguageChanged += Res_DisplayLanguageChanged; Draw += AdvancedToolTip_Draw; ResetOwnerDraw(); } - private void ResetOwnerDraw() => OwnerDraw = LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft; + private void ResetOwnerDraw() => OwnerDraw = Res.DisplayLanguage.TextInfo.IsRightToLeft; #endregion #region Event Handlers - private void LanguageSettings_DisplayLanguageChanged(object sender, EventArgs e) => ResetOwnerDraw(); + private void Res_DisplayLanguageChanged(object sender, EventArgs e) => ResetOwnerDraw(); #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs index 4e807b5..56f66c6 100644 --- a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs +++ b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs @@ -149,7 +149,7 @@ internal static bool ConfirmMessage(string message, bool isYesDefault = true) private static DialogResult ShowMessageBox(string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1) { - MessageBoxOptions options = LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft ? MessageBoxOptions.RightAlign | MessageBoxOptions.RtlReading : default; + MessageBoxOptions options = Res.DisplayLanguage.TextInfo.IsRightToLeft ? MessageBoxOptions.RightAlign | MessageBoxOptions.RtlReading : default; IntPtr windowHook = IntPtr.Zero; // On Windows hooking messages to be able to localize the buttons diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index 1720f9a..cb76fbe 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -208,8 +208,8 @@ private void InitPropertyBindings() private void InitCommandBindings() { - CommandBindings.Add(OnDisplayLanguageChangedGlobalCommand) - .AddSource(typeof(LanguageSettings), nameof(LanguageSettings.DisplayLanguageChangedGlobal)); + CommandBindings.Add(OnDisplayLanguageChangedCommand) + .AddSource(typeof(Res), nameof(Res.DisplayLanguageChanged)); } private ErrorProvider CreateProvider(ValidationSeverity level) => new ErrorProvider(components) @@ -253,7 +253,7 @@ private void InvokeIfRequired(Action action) private void ApplyRightToLeft() { - RightToLeft rtl = LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft ? RightToLeft.Yes : RightToLeft.No; + RightToLeft rtl = Res.DisplayLanguage.TextInfo.IsRightToLeft ? RightToLeft.Yes : RightToLeft.No; if (RightToLeft == rtl) return; @@ -267,7 +267,7 @@ private void ApplyRightToLeft() #region Command Handlers - private void OnDisplayLanguageChangedGlobalCommand() => InvokeIfRequired(() => + private void OnDisplayLanguageChangedCommand() => InvokeIfRequired(() => { ApplyRightToLeft(); ApplyStringResources(); @@ -291,7 +291,11 @@ private void OnValidationResultsChangedCommand(ValidationResultsCollection? vali #region Explicit Interface Implementations - void IDisposable.Dispose() => InvokeIfRequired(Dispose); + void IDisposable.Dispose() + { + isRtlChanging = false; + InvokeIfRequired(Dispose); + } void IView.ShowDialog(IntPtr ownerHandle) { diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs index d7a716e..70353b5 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/DithererStrengthEditorControl.cs @@ -64,7 +64,7 @@ internal DithererStrengthEditorControl(IWindowsFormsEditorService editorService, private DithererStrengthEditorControl() { - RightToLeft = LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft ? RightToLeft.Yes : RightToLeft.No; + RightToLeft = Res.DisplayLanguage.TextInfo.IsRightToLeft ? RightToLeft.Yes : RightToLeft.No; InitializeComponent(); } diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs index b07b4c1..5250ae7 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/QuantizerThresholdEditorControl.cs @@ -62,7 +62,7 @@ internal QuantizerThresholdEditorControl(IWindowsFormsEditorService editorServic private QuantizerThresholdEditorControl() { - RightToLeft = LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft ? RightToLeft.Yes : RightToLeft.No; + RightToLeft = Res.DisplayLanguage.TextInfo.IsRightToLeft ? RightToLeft.Yes : RightToLeft.No; InitializeComponent(); } diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/DrawToolTipEventArgsExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/DrawToolTipEventArgsExtensions.cs index f406d1d..079c363 100644 --- a/KGySoft.Drawing.ImagingTools/View/_Extensions/DrawToolTipEventArgsExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/DrawToolTipEventArgsExtensions.cs @@ -35,7 +35,7 @@ internal static void DrawToolTipAdvanced(this DrawToolTipEventArgs e) e.DrawBorder(); var flags = TextFormatFlags.HidePrefix | TextFormatFlags.VerticalCenter | TextFormatFlags.LeftAndRightPadding; - if (LanguageSettings.DisplayLanguage.TextInfo.IsRightToLeft) + if (Res.DisplayLanguage.TextInfo.IsRightToLeft) flags |= TextFormatFlags.RightToLeft | TextFormatFlags.Right; e.DrawText(flags); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index 995c8ef..2410173 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -324,13 +324,13 @@ private void DoDownloadResources(object state) private void ApplyResources() { ResHelper.ReleaseAllResources(); - CultureInfo current = LanguageSettings.DisplayLanguage; + CultureInfo current = Res.DisplayLanguage; if (downloadedCultures.All(c => c.CultureName != current.Name)) return; // The current language is among the downloaded ones: applying it LanguageSettings.DynamicResourceManagersSource = ResourceManagerSources.CompiledAndResX; - ResHelper.RaiseLanguageChanged(); + Res.OnDisplayLanguageChanged(); } protected override void ApplyDisplayLanguage() => ResetItems(); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index 79f9644..d0ec905 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -16,9 +16,6 @@ #region Usings -using System.Diagnostics; -using System.Linq; -using KGySoft.Resources; #region Used Namespaces @@ -26,15 +23,18 @@ using System.Collections; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.Linq; using System.Resources; using KGySoft.ComponentModel; using KGySoft.CoreLibraries; using KGySoft.Drawing.ImagingTools.Model; using KGySoft.Reflection; +using KGySoft.Resources; #endregion @@ -104,7 +104,7 @@ internal EditResourcesViewModel(CultureInfo culture) resources = new Dictionary, bool)>(3, EnumComparer.Comparer); Set(String.Empty, false, nameof(Filter)); ResourceFiles = Enum.GetFlags().Select(lib => new KeyValuePair(lib, ToFileName(lib))).ToArray(); - ApplyResourcesCommandState.Enabled = !Equals(LanguageSettings.DisplayLanguage, culture); + ApplyResourcesCommandState.Enabled = !Equals(Res.DisplayLanguage, culture); UpdateTitle(); SelectedLibrary = LocalizableLibraries.ImagingTools; } @@ -341,10 +341,10 @@ private void ApplyResources() // in which case the configuration may remain unsaved. SaveConfigurationCallback?.Invoke(); LanguageSettings.DynamicResourceManagersSource = ResourceManagerSources.CompiledAndResX; - if (Equals(LanguageSettings.DisplayLanguage, culture)) - ResHelper.RaiseLanguageChanged(); + if (Equals(Res.DisplayLanguage, culture)) + Res.OnDisplayLanguageChanged(); else - LanguageSettings.DisplayLanguage = culture; + Res.DisplayLanguage = culture; SetModified(false); } @@ -372,7 +372,7 @@ private void FilteredSet_ListChanged(object sender, ListChangedEventArgs e) private void OnApplyResourcesCommand(ICommandState state) { - Debug.Assert(IsModified || !Equals(culture, LanguageSettings.DisplayLanguage)); + Debug.Assert(IsModified || !Equals(culture, Res.DisplayLanguage)); ResHelper.ReleaseAllResources(); bool success = TrySaveResources(); if (success) @@ -388,7 +388,7 @@ private void OnSaveResourcesCommand(ICommandState state) success = TrySaveResources(); if (success) { - if (Equals(LanguageSettings.DisplayLanguage, culture)) + if (Equals(Res.DisplayLanguage, culture)) ApplyResources(); } } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index 4c8bad1..e8e72df 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -107,7 +107,7 @@ private List SelectableLanguages internal LanguageSettingsViewModel() { ResHelper.SavePendingResources(); // generates resource file for possibly non-existing language came from configuration - CurrentLanguage = LanguageSettings.DisplayLanguage; + CurrentLanguage = Res.DisplayLanguage; AllowResXResources = Configuration.AllowResXResources; UseOSLanguage = Configuration.UseOSLanguage; ExistingLanguagesOnly = true; // could be the default value but this way we spare one reset when initializing binding @@ -194,7 +194,7 @@ private void UpdateApplyCommandState() // Apply is enabled if current language is different than display language, // or when turning on/off .resx resources for the default language matters because it also has a resource file CultureInfo selected = CurrentLanguage; - ApplyCommandState.Enabled = !Equals(selected, LanguageSettings.DisplayLanguage) + ApplyCommandState.Enabled = !Equals(selected, Res.DisplayLanguage) || (Equals(selected, Res.DefaultLanguage) && (AllowResXResources ^ LanguageSettings.DynamicResourceManagersSource != ResourceManagerSources.CompiledOnly) && AvailableLanguages.Contains(Res.DefaultLanguage)); @@ -211,10 +211,10 @@ private void ApplyAndSave() CultureInfo currentLanguage = CurrentLanguage; LanguageSettings.DynamicResourceManagersSource = AllowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; - if (Equals(LanguageSettings.DisplayLanguage, currentLanguage)) - ResHelper.RaiseLanguageChanged(); + if (Equals(Res.DisplayLanguage, currentLanguage)) + Res.OnDisplayLanguageChanged(); else - LanguageSettings.DisplayLanguage = currentLanguage; + Res.DisplayLanguage = currentLanguage; // Note: Ensure is not really needed because main .resx is generated, while others are saved on demand in the editor, too //ResHelper.EnsureResourcesGenerated(); // TODO If used, then add to EditResourcesVM.Save, too, to be consistent diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs index 109af08..3eb1971 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs @@ -17,11 +17,8 @@ #region Usings using System; -using System.Globalization; -using System.Threading; using KGySoft.ComponentModel; -using KGySoft.Resources; #endregion @@ -47,20 +44,7 @@ internal abstract class ViewModelBase : ObservableObjectBase, IViewModel #region Constructors - protected ViewModelBase() - { - // Applying the configuration settings in each VM might seem to be an overkill when executed as a regular application but by using the factories from a - // consumer code any VM can be created in any order, in any thread. The VS extension creates the default VM always in a new thread, for example. - bool allowResXResources = Configuration.AllowResXResources; - CultureInfo desiredDisplayLanguage = allowResXResources - ? Configuration.UseOSLanguage ? Res.OSLanguage : Configuration.DisplayLanguage // here, allowing specific languages, too - : Res.DefaultLanguage; - - LanguageSettings.DisplayLanguage = Equals(desiredDisplayLanguage, CultureInfo.InvariantCulture) ? Res.DefaultLanguage : desiredDisplayLanguage; - LanguageSettings.DynamicResourceManagersSource = allowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; - - LanguageSettings.DisplayLanguageChangedGlobal += LanguageSettings_DisplayLanguageChangedGlobal; - } + protected ViewModelBase() => Res.DisplayLanguageChanged += Res_DisplayLanguageChanged; #endregion @@ -103,7 +87,7 @@ protected override void Dispose(bool disposing) if (IsDisposed) return; - LanguageSettings.DisplayLanguageChangedGlobal -= LanguageSettings_DisplayLanguageChangedGlobal; + Res.DisplayLanguageChanged -= Res_DisplayLanguageChanged; base.Dispose(disposing); } @@ -117,25 +101,8 @@ protected override void Dispose(bool disposing) #region Event Handlers - private void LanguageSettings_DisplayLanguageChangedGlobal(object sender, EventArgs e) + private void Res_DisplayLanguageChanged(object sender, EventArgs e) { - // As we get notification from any thread we read the current thread settings from the configuration - bool allowResXResources = Configuration.AllowResXResources; - CultureInfo desiredDisplayLanguage = allowResXResources - ? Configuration.UseOSLanguage ? Res.OSLanguage : Configuration.DisplayLanguage - : Res.DefaultLanguage; - if (Equals(desiredDisplayLanguage, CultureInfo.InvariantCulture)) - desiredDisplayLanguage = Res.DefaultLanguage; - - // The event was raised from another thread - if (!Equals(LanguageSettings.DisplayLanguage, desiredDisplayLanguage)) - { - LanguageSettings.DisplayLanguage = desiredDisplayLanguage; - - // we exit here because the line above end up calling this handler again from current thread - return; - } - // Trying to apply the new language in the thread of the corresponding view if (!TryInvokeSync(ApplyDisplayLanguage)) ApplyDisplayLanguage(); diff --git a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs index 50d1663..fa8117a 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs @@ -70,7 +70,7 @@ public static class InstallationManager /// /// Gets version of the currently used ImagingTools application. /// - public static Version ImagingToolsVersion { get; } = typeof(Configuration).Assembly.GetName().Version; + public static Version ImagingToolsVersion { get; } = typeof(InstallationManager).Assembly.GetName().Version; #endregion diff --git a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs index 32c2a79..ee67c70 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/ResHelper.cs @@ -122,7 +122,7 @@ internal static HashSet GetAvailableLanguages() internal static void EnsureResourcesGenerated() { foreach (DynamicResourceManager resourceManager in KnownResourceManagers) - resourceManager.GetExpandoResourceSet(LanguageSettings.DisplayLanguage, ResourceSetRetrieval.CreateIfNotExists, true); + resourceManager.GetExpandoResourceSet(Res.DisplayLanguage, ResourceSetRetrieval.CreateIfNotExists, true); } /// @@ -145,11 +145,6 @@ internal static void ReleaseAllResources() resourceManager.ReleaseAllResources(); } - /// - /// TODO: Remove when LanguageSettings.RaiseLanguageChanged will be available. - /// - internal static void RaiseLanguageChanged() => Reflector.InvokeMethod(typeof(LanguageSettings), "OnDisplayLanguageChanged", EventArgs.Empty); - #endregion } } From 4b348baa11ec4e75aac21b0d6993d06660147b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 24 Jun 2021 17:30:18 +0200 Subject: [PATCH 151/211] Managing installations: Detecting if the executed debugger instance is about to be removed while the VS package is installed --- .../_Classes/InstallationManager.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs index fa8117a..0e95315 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs @@ -129,7 +129,12 @@ public static void Install(string directory, out string? error, out string? warn Uninstall(directory, out error); if (error != null) + { + // VS issue workaround, see the comment in Uninstall + if (error == Res.ErrorMessageInstallationCannotBeRemoved) + error = Res.ErrorMessageInstallationCannotBeOverwritten; return; + } foreach (string file in files) { @@ -208,7 +213,8 @@ public static void Uninstall(string directory, out string? error) if (!Directory.Exists(directory)) return; - if (directory == Files.GetExecutingPath()) + string executingPath = Files.GetExecutingPath(); + if (directory == executingPath) { error = Res.ErrorMessageInstallationCannotBeRemoved; return; @@ -226,7 +232,13 @@ public static void Uninstall(string directory, out string? error) } catch (Exception e) when (!e.IsCritical()) { - error = Res.ErrorMessageCouldNotDeleteFile(file, e.Message); + // VS issue workaround: if the package is installed and we try to remove the executed version during debugging, then the executing path will be + // the package installation path in uppercase, instead of the target directory (the path is the directory if the package is not installed though). + // However, the same path is in lowercase if we start the removing from the VS Tools menu instead of debugging... + if (executingPath == executingPath.ToUpperInvariant() && e is UnauthorizedAccessException) + error = Res.ErrorMessageInstallationCannotBeRemoved; + else + error = Res.ErrorMessageCouldNotDeleteFile(file, e.Message); return; } } From 2f2f59a9ebdd50ab104754066d28ca876fa06a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 24 Jun 2021 18:33:58 +0200 Subject: [PATCH 152/211] Hiding OK/Cancel button in read-only context --- .../View/Forms/AppMainForm.Designer.cs | 1 - .../Forms/ColorVisualizerForm.Designer.cs | 7 ++--- .../View/Forms/ColorVisualizerForm.cs | 16 +++++++++++ .../Forms/ImageVisualizerForm.Designer.cs | 27 +++++++++---------- .../View/Forms/ImageVisualizerForm.cs | 7 +++++ .../View/Forms/PaletteVisualizerForm.cs | 16 +++++++++++ 6 files changed, 56 insertions(+), 18 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.Designer.cs index 11dbc58..06f191a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/AppMainForm.Designer.cs @@ -35,7 +35,6 @@ private void InitializeComponent() this.ClientSize = new System.Drawing.Size(484, 311); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable; this.Name = "AppMainForm"; - this.Padding = new System.Windows.Forms.Padding(0); this.ResumeLayout(false); this.PerformLayout(); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.Designer.cs index c5e2355..afd85d2 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.Designer.cs @@ -27,13 +27,14 @@ private void InitializeComponent() this.ucColorVisualizer.Dock = System.Windows.Forms.DockStyle.Fill; this.ucColorVisualizer.Location = new System.Drawing.Point(0, 0); this.ucColorVisualizer.Name = "ucColorVisualizer"; - this.ucColorVisualizer.Size = new System.Drawing.Size(244, 201); + this.ucColorVisualizer.Size = new System.Drawing.Size(244, 186); this.ucColorVisualizer.TabIndex = 1; // // okCancelButtons // + this.okCancelButtons.BackColor = System.Drawing.Color.Transparent; this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; - this.okCancelButtons.Location = new System.Drawing.Point(0, 201); + this.okCancelButtons.Location = new System.Drawing.Point(0, 186); this.okCancelButtons.Name = "okCancelButtons"; this.okCancelButtons.Size = new System.Drawing.Size(244, 35); this.okCancelButtons.TabIndex = 2; @@ -42,7 +43,7 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(244, 241); + this.ClientSize = new System.Drawing.Size(244, 221); this.Controls.Add(this.ucColorVisualizer); this.Controls.Add(this.okCancelButtons); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs index cf8a900..8eb00a6 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs @@ -76,6 +76,19 @@ protected override void OnFormClosing(FormClosingEventArgs e) base.OnFormClosing(e); } + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + switch (keyData) + { + case Keys.Escape when ViewModel.ReadOnly: // if not ReadOnly, use the Cancel button + DialogResult = DialogResult.Cancel; + return true; + + default: + return base.ProcessCmdKey(ref msg, keyData); + } + } + protected override void Dispose(bool disposing) { if (disposing) @@ -90,6 +103,9 @@ protected override void Dispose(bool disposing) private void InitPropertyBindings() { + // !VM.ReadOnly -> okCancelButtons.Visible + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.ReadOnly), nameof(okCancelButtons.Visible), ro => ro is false, okCancelButtons); + // VM.ReadOnly -> ucColorVisualizer.ReadOnly CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.ReadOnly), nameof(ucColorVisualizer.ReadOnly), ucColorVisualizer); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs index 6517d50..a6a8170 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs @@ -74,9 +74,9 @@ private void InitializeComponent() // imageViewer // this.imageViewer.Dock = System.Windows.Forms.DockStyle.Fill; - this.imageViewer.Location = new System.Drawing.Point(3, 49); + this.imageViewer.Location = new System.Drawing.Point(0, 49); this.imageViewer.Name = "imageViewer"; - this.imageViewer.Size = new System.Drawing.Size(328, 106); + this.imageViewer.Size = new System.Drawing.Size(364, 106); this.imageViewer.TabIndex = 1; this.imageViewer.TabStop = false; // @@ -88,21 +88,21 @@ private void InitializeComponent() this.lblNotification.Dock = System.Windows.Forms.DockStyle.Top; this.lblNotification.ForeColor = System.Drawing.Color.Black; this.lblNotification.ImageAlign = System.Drawing.ContentAlignment.MiddleRight; - this.lblNotification.Location = new System.Drawing.Point(3, 25); + this.lblNotification.Location = new System.Drawing.Point(0, 25); this.lblNotification.Name = "lblNotification"; this.lblNotification.Padding = new System.Windows.Forms.Padding(3, 3, 20, 3); - this.lblNotification.Size = new System.Drawing.Size(328, 24); + this.lblNotification.Size = new System.Drawing.Size(364, 24); this.lblNotification.TabIndex = 4; this.lblNotification.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // // splitter // this.splitter.Dock = System.Windows.Forms.DockStyle.Bottom; - this.splitter.Location = new System.Drawing.Point(3, 155); + this.splitter.Location = new System.Drawing.Point(0, 155); this.splitter.MinExtra = 16; this.splitter.MinSize = 50; this.splitter.Name = "splitter"; - this.splitter.Size = new System.Drawing.Size(328, 3); + this.splitter.Size = new System.Drawing.Size(364, 3); this.splitter.TabIndex = 3; this.splitter.TabStop = false; // @@ -125,9 +125,9 @@ private void InitializeComponent() this.toolStripSeparator4, this.btnAbout, this.btnConfiguration}); - this.tsMenu.Location = new System.Drawing.Point(3, 0); + this.tsMenu.Location = new System.Drawing.Point(0, 0); this.tsMenu.Name = "tsMenu"; - this.tsMenu.Size = new System.Drawing.Size(328, 25); + this.tsMenu.Size = new System.Drawing.Size(364, 25); this.tsMenu.TabIndex = 2; // // btnZoom @@ -431,12 +431,12 @@ private void InitializeComponent() // txtInfo // this.txtInfo.Dock = System.Windows.Forms.DockStyle.Bottom; - this.txtInfo.Location = new System.Drawing.Point(3, 158); + this.txtInfo.Location = new System.Drawing.Point(0, 158); this.txtInfo.Multiline = true; this.txtInfo.Name = "txtInfo"; this.txtInfo.ReadOnly = true; this.txtInfo.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.txtInfo.Size = new System.Drawing.Size(328, 123); + this.txtInfo.Size = new System.Drawing.Size(364, 123); this.txtInfo.TabIndex = 0; this.txtInfo.TabStop = false; this.txtInfo.WordWrap = false; @@ -445,16 +445,16 @@ private void InitializeComponent() // this.okCancelButtons.BackColor = System.Drawing.Color.Transparent; this.okCancelButtons.Dock = System.Windows.Forms.DockStyle.Bottom; - this.okCancelButtons.Location = new System.Drawing.Point(3, 281); + this.okCancelButtons.Location = new System.Drawing.Point(0, 281); this.okCancelButtons.Name = "okCancelButtons"; - this.okCancelButtons.Size = new System.Drawing.Size(328, 35); + this.okCancelButtons.Size = new System.Drawing.Size(364, 35); this.okCancelButtons.TabIndex = 5; // // ImageVisualizerForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(334, 316); + this.ClientSize = new System.Drawing.Size(364, 316); this.Controls.Add(this.imageViewer); this.Controls.Add(this.lblNotification); this.Controls.Add(this.splitter); @@ -464,7 +464,6 @@ private void InitializeComponent() this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; this.MinimumSize = new System.Drawing.Size(200, 200); this.Name = "ImageVisualizerForm"; - this.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.tsMenu.ResumeLayout(false); this.tsMenu.PerformLayout(); this.ResumeLayout(false); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index d0b9ea6..eae61e0 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -156,6 +156,10 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData) case Keys.Shift | Keys.Left: (RightToLeft == RightToLeft.Yes ? btnNext : btnPrev).PerformClick(); return true; + case Keys.Escape when ViewModel.ReadOnly: // if not ReadOnly, use the Cancel button if available + DialogResult = DialogResult.Cancel; + return true; + default: return base.ProcessCmdKey(ref msg, keyData); } @@ -194,6 +198,9 @@ private void InitViewModelDependencies() private void InitPropertyBindings() { + // !VM.ReadOnly -> okCancelButtons.Visible + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.ReadOnly), nameof(okCancelButtons.Visible), ro => ro is false, okCancelButtons); + // VM.Notification -> lblNotification.Text CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Notification), nameof(Label.Text), lblNotification); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs index c03f43e..9ebf859 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs @@ -91,6 +91,19 @@ protected override void OnFormClosing(FormClosingEventArgs e) base.OnFormClosing(e); } + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + switch (keyData) + { + case Keys.Escape when ViewModel.ReadOnly: // if not ReadOnly, use the Cancel button + DialogResult = DialogResult.Cancel; + return true; + + default: + return base.ProcessCmdKey(ref msg, keyData); + } + } + protected override void Dispose(bool disposing) { if (disposing) @@ -104,6 +117,9 @@ protected override void Dispose(bool disposing) private void InitPropertyBindings() { + // !VM.ReadOnly -> okCancelButtons.Visible + CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.ReadOnly), nameof(okCancelButtons.Visible), ro => ro is false, okCancelButtons); + // VM.Palette -> pnlPalette.Palette CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Palette), nameof(pnlPalette.Palette), pnlPalette); From 2b073289474573e2603d2f536e737c8b1c8c2216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 24 Jun 2021 18:52:48 +0200 Subject: [PATCH 153/211] Not showing OK/Cancel buttons when executed as an application --- .../View/Forms/ImageVisualizerForm.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index eae61e0..760eda1 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -198,8 +198,9 @@ private void InitViewModelDependencies() private void InitPropertyBindings() { - // !VM.ReadOnly -> okCancelButtons.Visible - CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.ReadOnly), nameof(okCancelButtons.Visible), ro => ro is false, okCancelButtons); + // not as binding because will not change and we don't need the buttons for main form + if (ViewModel.ReadOnly) + okCancelButtons.Visible = false; // VM.Notification -> lblNotification.Text CommandBindings.AddPropertyBinding(ViewModel, nameof(ViewModel.Notification), nameof(Label.Text), lblNotification); From 448382de61b3c8e19fe5028855f0051ae8acf2fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 24 Jun 2021 19:48:37 +0200 Subject: [PATCH 154/211] Fixing analyzer warnings --- ...ft.Drawing.DebuggerVisualizers.Package.csproj | 1 + ...ySoft.Drawing.DebuggerVisualizers.Test.csproj | 3 +++ .../ViewModel/TestObjectProvider.cs | 2 +- .../KGySoft.Drawing.DebuggerVisualizers.csproj | 3 +++ .../GlobalSuppressions.cs | 1 + .../KGySoft.Drawing.ImagingTools.csproj | 3 +++ .../Model/ResourceEntry.cs | 2 +- KGySoft.Drawing.ImagingTools/Res.cs | 2 +- .../View/Components/AdvancedToolTip.cs | 2 +- .../View/Controls/AdvancedDataGridView.cs | 9 +++++++++ .../View/Controls/AutoMirrorPanel.cs | 9 +++++++++ .../View/Controls/CheckGroupBox.cs | 9 +++++++++ .../ImageViewer.DisplayImageGenerator.cs | 4 ++-- .../View/Controls/PalettePanel.cs | 3 ++- .../View/Controls/ProgressFooter.cs | 2 +- KGySoft.Drawing.ImagingTools/View/Dialogs.cs | 2 +- .../View/Forms/ImageVisualizerForm.cs | 9 +++++++++ .../View/UserControls/ColorVisualizerControl.cs | 4 ++-- .../View/_Extensions/ControlExtensions.cs | 10 ++++++++++ .../ViewModel/DownloadResourcesViewModel.cs | 9 +++++---- .../DownloadableResourceItemCollection.cs | 2 +- .../ViewModel/EditResourcesViewModel.cs | 16 ++++++++++++++-- .../ViewModel/ImageVisualizerViewModel.cs | 4 ++-- .../ViewModel/TransformBitmapViewModelBase.cs | 2 +- .../ViewModel/ViewModelBase.cs | 2 +- .../_Classes/Configuration.cs | 2 +- .../_Classes/InstallationManager.cs | 2 +- 27 files changed, 95 insertions(+), 24 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj b/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj index e0c4bba..4c1836b 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj @@ -64,6 +64,7 @@ + diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj b/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj index e2d13b7..776578f 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj @@ -17,6 +17,9 @@ WinExe app.manifest enable + + + $(NoWarn);NETSDK1138 diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/TestObjectProvider.cs b/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/TestObjectProvider.cs index d68b29a..95be35d 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/TestObjectProvider.cs +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/TestObjectProvider.cs @@ -86,7 +86,7 @@ public object GetObject() public void ReplaceObject(object newObject) { - Object = newObject.DeepClone(); + Object = newObject.DeepClone(null); ObjectReplaced = true; } diff --git a/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj b/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj index 8d2472e..48e7112 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj @@ -15,6 +15,9 @@ LICENSE György Kőszeg enable + + + $(NoWarn);NETSDK1138 diff --git a/KGySoft.Drawing.ImagingTools/GlobalSuppressions.cs b/KGySoft.Drawing.ImagingTools/GlobalSuppressions.cs index dec7e59..df6aa49 100644 --- a/KGySoft.Drawing.ImagingTools/GlobalSuppressions.cs +++ b/KGySoft.Drawing.ImagingTools/GlobalSuppressions.cs @@ -9,3 +9,4 @@ [assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "Decided individually")] [assembly: SuppressMessage("Style", "IDE0057:Use range operator", Justification = "Cannot be used because it is not supported in every targeted platform")] [assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "Decided individually")] +[assembly: SuppressMessage("Style", "IDE0042:Deconstruct variable declaration", Justification = "Decided individually")] diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj index d47ac4a..c8a14ab 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj @@ -17,6 +17,9 @@ WinExe app.manifest enable + + + $(NoWarn);NETSDK1138 diff --git a/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs b/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs index 7e230ff..9b416f4 100644 --- a/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs +++ b/KGySoft.Drawing.ImagingTools/Model/ResourceEntry.cs @@ -51,7 +51,7 @@ private enum State public string Key { get; } public string OriginalText { get; } - public string TranslatedText { get => Get(); set => Set(value); } + public string TranslatedText { get => Get(); init => Set(value); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 5de7209..5f1b5fd 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -70,7 +70,7 @@ internal static class Res /// Similar to , which is not quite reliable when executing as a debugger visualizer because /// Visual Studio resets the on every keystroke and other events. /// - internal static event EventHandler DisplayLanguageChanged + internal static event EventHandler? DisplayLanguageChanged { add => value.AddSafe(ref displayLanguageChanged); remove => value.RemoveSafe(ref displayLanguageChanged); diff --git a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs index 5f02a0d..1f57a6f 100644 --- a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolTip.cs @@ -76,7 +76,7 @@ private void Initialize() #region Event Handlers - private void Res_DisplayLanguageChanged(object sender, EventArgs e) => ResetOwnerDraw(); + private void Res_DisplayLanguageChanged(object? sender, EventArgs e) => ResetOwnerDraw(); #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs index 159cd1b..40c6c4d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedDataGridView.cs @@ -29,6 +29,15 @@ #endregion +#region Suppressions + +#if NETCOREAPP3_0 +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. - Columns items are never null +#pragma warning disable CS8602 // Dereference of a possibly null reference. - Columns items are never null +#endif + +#endregion + namespace KGySoft.Drawing.ImagingTools.View.Controls { #region Usings diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AutoMirrorPanel.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AutoMirrorPanel.cs index 71fbae6..b63f51c 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AutoMirrorPanel.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AutoMirrorPanel.cs @@ -6,6 +6,15 @@ #endregion +#region Suppressions + +#if NETCOREAPP3_0 +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. - Controls items are never null +#pragma warning disable CS8602 // Dereference of a possibly null reference. - Controls items are never null +#endif + +#endregion + namespace KGySoft.Drawing.ImagingTools.View.Controls { /// diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs index e9d1370..6f764bf 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs @@ -26,6 +26,15 @@ #endregion +#region Suppressions + +#if NETCOREAPP3_0 +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. - Controls items are never null +#pragma warning disable CS8604 // Possible null reference argument. - Controls items are never null +#endif + +#endregion + namespace KGySoft.Drawing.ImagingTools.View.Controls { internal partial class CheckGroupBox : GroupBox, ICustomLocalizable diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs index c103c30..15221ab 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.DisplayImageGenerator.cs @@ -347,7 +347,7 @@ private void BeginGenerateDefaultDisplayImageIfNeeded() InvalidateOwner = bitmap!.RawFormat.Guid == ImageFormat.Icon.Guid }; generateDefaultImageTask = task; - ThreadPool.QueueUserWorkItem(GenerateDefaultImage, task); + ThreadPool.QueueUserWorkItem(GenerateDefaultImage!, task); } private void BeginGenerateResizedDisplayImageIfNeeded() @@ -396,7 +396,7 @@ private void BeginGenerateResizedDisplayImageIfNeeded() }; generateResizedImageTask = task; - ThreadPool.QueueUserWorkItem(GenerateResizedImage, task); + ThreadPool.QueueUserWorkItem(GenerateResizedImage!, task); } private void GenerateDefaultImage(object state) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs index dc1a906..fb4f607 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs @@ -45,6 +45,8 @@ internal sealed partial class PalettePanel : BaseControl #region Instance Fields + private readonly int scrollbarWidth; + private IList? palette; private int selectedColorIndex = -1; private int firstVisibleColor; @@ -52,7 +54,6 @@ internal sealed partial class PalettePanel : BaseControl private int counter; private PointF scale = new PointF(1f, 1f); private int scrollFraction; - private int scrollbarWidth; private bool isRightToLeft; #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs index c92d4b6..e29c852 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ProgressFooter.cs @@ -168,7 +168,7 @@ protected override void Dispose(bool disposing) #region Event handlers #pragma warning disable IDE1006 // Naming Styles - private void lblProgress_TextChanged(object sender, EventArgs e) => lblProgress.Width = lblProgress.PreferredWidth; + private void lblProgress_TextChanged(object? sender, EventArgs e) => lblProgress.Width = lblProgress.PreferredWidth; private void timer_Tick(object? sender, EventArgs e) => UpdateProgress(); diff --git a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs index 56f66c6..54c1fc1 100644 --- a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs +++ b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs @@ -174,7 +174,7 @@ private static IntPtr CallWndRetProc(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0) { - var msg = (CWPRETSTRUCT)Marshal.PtrToStructure(lParam, typeof(CWPRETSTRUCT)); + var msg = (CWPRETSTRUCT)Marshal.PtrToStructure(lParam, typeof(CWPRETSTRUCT))!; if (msg.message == Constants.WM_INITDIALOG) { string name = User32.GetClassName(msg.hwnd); diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index 760eda1..564983f 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -26,6 +26,15 @@ #endregion +#region Suppressions + +#if NETCOREAPP3_0 +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. - DropDownItems items are never null +#pragma warning disable CS8602 // Dereference of a possibly null reference. - DropDownItems items are never null +#endif + +#endregion + namespace KGySoft.Drawing.ImagingTools.View.Forms { internal partial class ImageVisualizerForm : MvvmBaseForm diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs index 2897281..d3d4f3e 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs @@ -184,8 +184,8 @@ public ColorVisualizerControl() #region Static Methods - private static string GetKnownColor(Color color) => KnownColors.GetValueOrDefault(color.ToArgb(), "–"); - private static string GetSystemColors(Color color) => SystemColors.GetValueOrDefault(color.ToArgb(), "–"); + private static string GetKnownColor(Color color) => KnownColors.GetValueOrDefault(color.ToArgb(), "–")!; + private static string GetSystemColors(Color color) => SystemColors.GetValueOrDefault(color.ToArgb(), "–")!; #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs index 90af8ea..034d1f6 100644 --- a/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/ControlExtensions.cs @@ -24,6 +24,16 @@ #endregion +#region Suppressions + +#if NETCOREAPP3_0 +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. - Controls/Columns/DropDownItems never have null elements +#pragma warning disable CS8602 // Dereference of a possibly null reference. - Controls/Columns/DropDownItems never have null elements +#pragma warning disable CS8604 // Possible null reference argument. - Controls/Columns/DropDownItems never have null elements +#endif + +#endregion + namespace KGySoft.Drawing.ImagingTools.View { internal static class ControlExtensions diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index 2410173..97fbaef 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -107,9 +107,10 @@ public DownloadInfo(LocalizationInfo info, LocalizableLibraries library) #region Fields + private readonly List availableResources = new List(); + private readonly HashSet downloadedCultures = new HashSet(); + private volatile AsyncTaskBase? activeTask; - private volatile List availableResources = new List(); - private volatile HashSet downloadedCultures = new HashSet(); #endregion @@ -180,7 +181,7 @@ private void BeginDownloadManifest() { IsProcessing = true; activeTask = new DownloadTask { Uri = new Uri(Configuration.BaseUri, "manifest.xml") }; - ThreadPool.QueueUserWorkItem(DoDownloadManifest, activeTask); + ThreadPool.QueueUserWorkItem(DoDownloadManifest!, activeTask); } private void DoDownloadManifest(object state) @@ -240,7 +241,7 @@ private void BeginDownloadResources(List toDownload, bool overwrit DownloadCommandState.Enabled = false; IsProcessing = true; activeTask = new DownloadResourcesTask { Files = toDownload, Overwrite = overwrite }; - ThreadPool.QueueUserWorkItem(DoDownloadResources, activeTask); + ThreadPool.QueueUserWorkItem(DoDownloadResources!, activeTask); } private void DoDownloadResources(object state) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItemCollection.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItemCollection.cs index 718777f..9a06bd8 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItemCollection.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItemCollection.cs @@ -43,7 +43,7 @@ internal DownloadableResourceItemCollection(ICollection collec foreach (LocalizationInfo info in collection) { var item = new DownloadableResourceItem(info); - item.PropertyChanged += Item_PropertyChanged; + item.PropertyChanged += Item_PropertyChanged!; if (langGroups.TryGetValue(info.CultureName, out List? group)) group.Add(item); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index d0ec905..a51cb5c 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -46,6 +46,14 @@ #endregion +#region Suppressions + +#if NETCOREAPP3_0 +#pragma warning disable CS8605 // Unboxing a possibly null value. - false alarm for iterating through a non-generic dictionary +#endif + +#endregion + namespace KGySoft.Drawing.ImagingTools.ViewModel { internal class EditResourcesViewModel : ViewModelBase @@ -203,6 +211,10 @@ private void ApplySelection() ApplyFilter(set); } +#if NETCOREAPP3_0_OR_GREATER + [SuppressMessage("Usage", "CA2249:Consider using 'string.Contains' instead of 'string.IndexOf'", + Justification = "Cannot use Contains because it is not available on all targeted platforms")] +#endif private void ApplyFilter(IList set) { if (FilteredSet is SortableBindingList oldSet) @@ -267,8 +279,8 @@ private bool TryReadResources(LocalizableLibraries library, [MaybeNullWhen(false var result = new SortableBindingList(); foreach (DictionaryEntry entry in compiled) result.Add(new ResourceEntry((string)entry.Key, - (string)entry.Value, - resourceManger.GetString((string)entry.Key, culture) ?? LanguageSettings.UntranslatedResourcePrefix + (string)entry.Value)); + (string)entry.Value!, + resourceManger.GetString((string)entry.Key, culture) ?? LanguageSettings.UntranslatedResourcePrefix + (string)entry.Value!)); error = null; set = result; diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs index c6c17d6..05f636f 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs @@ -1229,10 +1229,10 @@ private void OnShowAboutCommand() #if NET35 const string frameworkName = ".NET Framework 3.5"; #else - var attr = (TargetFrameworkAttribute)Attribute.GetCustomAttribute(asm, typeof(TargetFrameworkAttribute)); + TargetFrameworkAttribute attr = (TargetFrameworkAttribute)Attribute.GetCustomAttribute(asm, typeof(TargetFrameworkAttribute))!; string frameworkName = attr.FrameworkDisplayName ?? attr.FrameworkName; #endif - ShowInfo(Res.InfoMessageAbout(asm.GetName().Version, frameworkName, DateTime.Now.Year)); + ShowInfo(Res.InfoMessageAbout(asm.GetName().Version!, frameworkName, DateTime.Now.Year)); } #endregion diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs index b79faf9..ebb307b 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs @@ -207,7 +207,7 @@ protected void BeginGeneratePreview() // Not awaiting the canceled task here to prevent the UI from lagging. IsGenerating = true; - ThreadPool.QueueUserWorkItem(DoGenerate, CreateGenerateTask()); + ThreadPool.QueueUserWorkItem(DoGenerate!, CreateGenerateTask()); } protected abstract GenerateTaskBase CreateGenerateTask(); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs index 3eb1971..9d0970d 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs @@ -101,7 +101,7 @@ protected override void Dispose(bool disposing) #region Event Handlers - private void Res_DisplayLanguageChanged(object sender, EventArgs e) + private void Res_DisplayLanguageChanged(object? sender, EventArgs e) { // Trying to apply the new language in the thread of the corresponding view if (!TryInvokeSync(ApplyDisplayLanguage)) diff --git a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs index aeaa059..f047691 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs @@ -166,7 +166,7 @@ private static void SetInSettings(object value, [CallerMemberName]string propert #region Event handlers - private static Assembly? CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + private static Assembly? CurrentDomain_AssemblyResolve(object? sender, ResolveEventArgs args) { #if NETFRAMEWORK if (args.Name.StartsWith("System, Version=", StringComparison.Ordinal)) diff --git a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs index 0e95315..30a3d8b 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/InstallationManager.cs @@ -70,7 +70,7 @@ public static class InstallationManager /// /// Gets version of the currently used ImagingTools application. /// - public static Version ImagingToolsVersion { get; } = typeof(InstallationManager).Assembly.GetName().Version; + public static Version ImagingToolsVersion { get; } = typeof(InstallationManager).Assembly.GetName().Version!; #endregion From eaf16fb4c73f6152e649b8ad4d82181db8c7aaa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 25 Jun 2021 11:55:06 +0200 Subject: [PATCH 155/211] New IsViewLoaded property --- .../ViewModel/GraphicsVisualizerViewModel.cs | 9 +-------- .../ViewModel/TransformBitmapViewModelBase.cs | 6 ++---- .../ViewModel/ViewModelBase.cs | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/GraphicsVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/GraphicsVisualizerViewModel.cs index 6035a5c..6bf5126 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/GraphicsVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/GraphicsVisualizerViewModel.cs @@ -31,12 +31,6 @@ namespace KGySoft.Drawing.ImagingTools.ViewModel { internal class GraphicsVisualizerViewModel : ImageVisualizerViewModel { - #region Fields - - private bool viewInitialized; - - #endregion - #region Properties internal GraphicsInfo? GraphicsInfo { get => Get(); init => Set(value); } @@ -74,7 +68,6 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) internal override void ViewLoaded() { - viewInitialized = true; UpdateGraphicImage(); base.ViewLoaded(); } @@ -150,7 +143,7 @@ private void UpdateImageAndCommands() private void UpdateGraphicImage() { - if (!viewInitialized) + if (!IsViewLoaded) return; GraphicsInfo? graphicsInfo = GraphicsInfo; Bitmap? backingImage = graphicsInfo?.GraphicsImage; diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs index ebb307b..b89c551 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/TransformBitmapViewModelBase.cs @@ -52,7 +52,6 @@ protected abstract class GenerateTaskBase : AsyncTaskBase private readonly object syncRoot = new object(); private volatile GenerateTaskBase? activeTask; - private bool initializing = true; private bool keepResult; private DrawingProgressManager? drawingProgressManager; @@ -127,7 +126,6 @@ internal override void ViewLoaded() { // could be in constructor but we only need it when there is a view drawingProgressManager = new DrawingProgressManager(TrySetProgress); - initializing = false; base.ViewLoaded(); } @@ -158,7 +156,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) return; default: - if (initializing || !AffectsPreview(e.PropertyName!)) + if (!IsViewLoaded || !AffectsPreview(e.PropertyName!)) return; Validate(); ResetCommandState.Enabled = AreSettingsChanged; @@ -169,7 +167,7 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) protected void Validate() { - if (initializing) + if (!IsViewLoaded) return; ValidationResults = DoValidation(); } diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs index 9d0970d..3d5f7ce 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ViewModelBase.cs @@ -31,6 +31,8 @@ internal abstract class ViewModelBase : ObservableObjectBase, IViewModel { #region Properties + #region Internal Properties + internal Action? ShowErrorCallback { get => Get?>(); set => Set(value); } internal Action? ShowWarningCallback { get => Get?>(); set => Set(value); } internal Action? ShowInfoCallback { get => Get?>(); set => Set(value); } @@ -42,6 +44,14 @@ internal abstract class ViewModelBase : ObservableObjectBase, IViewModel #endregion + #region Protected Properties + + protected bool IsViewLoaded { get; private set; } + + #endregion + + #endregion + #region Constructors protected ViewModelBase() => Res.DisplayLanguageChanged += Res_DisplayLanguageChanged; @@ -95,7 +105,11 @@ protected override void Dispose(bool disposing) #region Internal Methods - internal virtual void ViewLoaded() => SetModified(false); + internal virtual void ViewLoaded() + { + IsViewLoaded = true; + SetModified(false); + } #endregion From cc8fad151c281af62dfb1c6bbef872713bdbeabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 25 Jun 2021 11:56:07 +0200 Subject: [PATCH 156/211] Edit resources: adjusting the state of the Apply command --- .../ViewModel/EditResourcesViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs index a51cb5c..8d538c5 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/EditResourcesViewModel.cs @@ -366,7 +366,7 @@ private void ApplyResources() private void FilteredSet_ListChanged(object sender, ListChangedEventArgs e) { - if (e.ListChangedType != ListChangedType.ItemChanged) + if (!IsViewLoaded || e.ListChangedType != ListChangedType.ItemChanged || e.PropertyDescriptor?.Name != nameof(ResourceEntry.TranslatedText)) return; ApplyResourcesCommandState.Enabled = true; From 80d72da95f3d3559dbc55cad94f2f345cbadc68d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 25 Jun 2021 13:00:52 +0200 Subject: [PATCH 157/211] Refactoring dialogs localization --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 10 ++++++++-- KGySoft.Drawing.ImagingTools/View/Dialogs.cs | 19 +++++++++---------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index a88e1ad..78e413d 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -938,10 +938,10 @@ Tip: use '[T]' to filter untranslated texts. &No - + &Basic Colors: - + Custo&m Colors: @@ -980,4 +980,10 @@ Tip: use '[T]' to filter untranslated texts. &Make New Folder + + Folder: + + + Select Folder + \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs index 54c1fc1..ea615df 100644 --- a/KGySoft.Drawing.ImagingTools/View/Dialogs.cs +++ b/KGySoft.Drawing.ImagingTools/View/Dialogs.cs @@ -35,8 +35,8 @@ internal static class Dialogs private enum DialogType { - MessageBoxSingleButton, - MessageBoxMoreButtons, + SingleButtonMessageBox, + MultiButtonMessageBox, ColorDialog, FolderDialog } @@ -158,7 +158,7 @@ private static DialogResult ShowMessageBox(string message, string caption, Messa windowHook = User32.HookCallWndRetProc(callWndRetProc); dialogContext = new DialogContext { - DialogType = buttons == MessageBoxButtons.OK ? DialogType.MessageBoxSingleButton : DialogType.MessageBoxMoreButtons + DialogType = buttons == MessageBoxButtons.OK ? DialogType.SingleButtonMessageBox : DialogType.MultiButtonMessageBox }; } @@ -199,23 +199,22 @@ private static bool EnumChildProc(IntPtr hWnd, IntPtr lParam) { string className = User32.GetClassName(hWnd); int id = User32.GetDialogControlId(hWnd); + if (id == 0) + return true; // Controls with id 65535 may duplicate on some dialogs. Usually these contain custom message but on color dialog - // these are also constant labels so we assign an incremental id for these. + // these are also constant labels so we assign incremental negative ids for them. if (id == UInt16.MaxValue && className == Constants.ClassNameStatic) { if (!dialogContext.AllowCustomStaticLocalization) return true; - className = $"{className}.{dialogContext.DialogType}"; - id = dialogContext.CustomStaticId; - dialogContext.CustomStaticId += 1; + id = --dialogContext.CustomStaticId; } // If there is a single OK button in a MessageBox it has the same id as a Cancel button. - else if (dialogContext.DialogType == DialogType.MessageBoxSingleButton && id == Constants.IDCANCEL && className == Constants.ClassNameButton) + else if (dialogContext.DialogType == DialogType.SingleButtonMessageBox && id == Constants.IDCANCEL && className == Constants.ClassNameButton) id = Constants.IDOK; - // Now the resource id is either "className.id" or "className.DialogType.id" - string? text = Res.GetStringOrNull($"{className}.{id}"); + string? text = Res.GetStringOrNull($"{dialogContext.DialogType}.{className}.{id}") ?? Res.GetStringOrNull($"{className}.{id}"); if (text != null) User32.SetControlText(hWnd, text); return true; From 605d03da26c3c77c9f40b22e1f4da63b6db57f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 25 Jun 2021 14:00:57 +0200 Subject: [PATCH 158/211] Using default language if the resources of the saved configuration is missing --- KGySoft.Drawing.ImagingTools/Res.cs | 2 +- .../ViewModel/LanguageSettingsViewModel.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 5f1b5fd..56430b0 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -344,7 +344,7 @@ static Res() ? Configuration.UseOSLanguage ? OSLanguage : Configuration.DisplayLanguage // here, allowing specific languages, too : DefaultLanguage; - if (Equals(displayLanguage, CultureInfo.InvariantCulture)) + if (Equals(displayLanguage, CultureInfo.InvariantCulture) || (!Equals(displayLanguage, DefaultLanguage) && !ResHelper.GetAvailableLanguages().Contains(displayLanguage))) displayLanguage = DefaultLanguage; DisplayLanguage = displayLanguage; LanguageSettings.DynamicResourceManagersSource = allowResXResources ? ResourceManagerSources.CompiledAndResX : ResourceManagerSources.CompiledOnly; diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs index e8e72df..42bd846 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/LanguageSettingsViewModel.cs @@ -106,7 +106,6 @@ private List SelectableLanguages internal LanguageSettingsViewModel() { - ResHelper.SavePendingResources(); // generates resource file for possibly non-existing language came from configuration CurrentLanguage = Res.DisplayLanguage; AllowResXResources = Configuration.AllowResXResources; UseOSLanguage = Configuration.UseOSLanguage; From e85f715efafdd412dd9b416e8fc9dc3818c36d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 25 Jun 2021 21:51:02 +0200 Subject: [PATCH 159/211] Fixing StackOverflowException when using Mono --- KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs index cb76fbe..0d38a5c 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/MvvmBaseForm.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Linq; using System.Threading; @@ -291,10 +292,12 @@ private void OnValidationResultsChangedCommand(ValidationResultsCollection? vali #region Explicit Interface Implementations + [SuppressMessage("CodeQuality", "IDE0002:Name can be simplified", + Justification = "Without the base qualifier executing in Mono causes StackOverflowException. See https://github.com/mono/mono/issues/21129")] void IDisposable.Dispose() { isRtlChanging = false; - InvokeIfRequired(Dispose); + InvokeIfRequired(base.Dispose); } void IView.ShowDialog(IntPtr ownerHandle) From 337c0ec577d6ddab65cd33318d82a362da865e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 26 Jun 2021 11:45:13 +0200 Subject: [PATCH 160/211] When adjusting sizes, considering notification height if it is visible. --- KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index 564983f..f11d784 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -373,7 +373,8 @@ private void AdjustSize() if (imageViewer.Height >= minHeight) return; int buttonsHeight = okCancelButtons.Visible ? okCancelButtons.Height : 0; - txtInfo.Height = ClientSize.Height - Padding.Vertical - tsMenu.Height - splitter.Height - buttonsHeight - minHeight; + int notificationHeight = lblNotification.Visible ? lblNotification.Height : 0; + txtInfo.Height = ClientSize.Height - Padding.Vertical - tsMenu.Height - splitter.Height - buttonsHeight - notificationHeight - minHeight; PerformLayout(); } From 2465e5d8f1dedd4f34422f4d42af97aa468582ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 26 Jun 2021 12:26:13 +0200 Subject: [PATCH 161/211] Fixing applying Palette/Color changes on Mono --- KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs | 2 +- .../View/Forms/PaletteVisualizerForm.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs index 8eb00a6..273af91 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ColorVisualizerForm.cs @@ -71,7 +71,7 @@ protected override void ApplyViewModel() protected override void OnFormClosing(FormClosingEventArgs e) { - if (e.CloseReason == CloseReason.UserClosing || DialogResult == DialogResult.Cancel) + if (DialogResult != DialogResult.OK) ViewModel.SetModified(false); base.OnFormClosing(e); } diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs index 9ebf859..8bcacdb 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/PaletteVisualizerForm.cs @@ -86,7 +86,7 @@ protected override void ApplyViewModel() protected override void OnFormClosing(FormClosingEventArgs e) { - if (e.CloseReason == CloseReason.UserClosing || DialogResult == DialogResult.Cancel) + if (DialogResult != DialogResult.OK) ViewModel.SetModified(false); base.OnFormClosing(e); } From a58a5f28bc4a6ea50cc929bd2af6a9ec9f6172fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 26 Jun 2021 13:59:09 +0200 Subject: [PATCH 162/211] Fixing read only text box appearance in high contrast mode --- .../View/Forms/EditResourcesForm.Designer.cs | 3 +++ .../View/Forms/ImageVisualizerForm.Designer.cs | 2 ++ .../View/UserControls/ColorVisualizerControl.Designer.cs | 2 ++ 3 files changed, 7 insertions(+) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs index 6dc6a1f..c2a9f4b 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.Designer.cs @@ -214,7 +214,9 @@ private void InitializeComponent() // // txtOriginalText // + this.txtOriginalText.BackColor = System.Drawing.SystemColors.Control; this.txtOriginalText.Dock = System.Windows.Forms.DockStyle.Fill; + this.txtOriginalText.ForeColor = System.Drawing.SystemColors.ControlText; this.txtOriginalText.Location = new System.Drawing.Point(3, 16); this.txtOriginalText.Multiline = true; this.txtOriginalText.Name = "txtOriginalText"; @@ -250,6 +252,7 @@ private void InitializeComponent() // okCancelApplyButtons // this.okCancelApplyButtons.ApplyButtonVisible = true; + this.okCancelApplyButtons.BackColor = System.Drawing.Color.Transparent; this.okCancelApplyButtons.Dock = System.Windows.Forms.DockStyle.Bottom; this.okCancelApplyButtons.Location = new System.Drawing.Point(3, 273); this.okCancelApplyButtons.Name = "okCancelApplyButtons"; diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs index a6a8170..f0de31b 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs @@ -430,7 +430,9 @@ private void InitializeComponent() // // txtInfo // + this.txtInfo.BackColor = System.Drawing.SystemColors.Control; this.txtInfo.Dock = System.Windows.Forms.DockStyle.Bottom; + this.txtInfo.ForeColor = System.Drawing.SystemColors.ControlText; this.txtInfo.Location = new System.Drawing.Point(0, 158); this.txtInfo.Multiline = true; this.txtInfo.Name = "txtInfo"; diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs index 4065440..0d63f87 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs @@ -247,7 +247,9 @@ private void InitializeComponent() // // txtColor // + this.txtColor.BackColor = System.Drawing.SystemColors.Control; this.txtColor.Dock = System.Windows.Forms.DockStyle.Fill; + this.txtColor.ForeColor = System.Drawing.SystemColors.ControlText; this.txtColor.Location = new System.Drawing.Point(0, 83); this.txtColor.Multiline = true; this.txtColor.Name = "txtColor"; From 96d8b3846d1274a775019e52046f94983ce7c8fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 26 Jun 2021 15:05:37 +0200 Subject: [PATCH 163/211] Fixing tool strip items appearance in high contrast mode --- .../AdvancedToolStripSplitButton.cs | 3 +- .../View/Controls/AdvancedToolStrip.cs | 48 ++++++++++++++----- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs index fbaab38..8a8705d 100644 --- a/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/AdvancedToolStripSplitButton.cs @@ -92,8 +92,9 @@ public override Size GetPreferredSize(Size constrainingSize) { if (Owner.Orientation == Orientation.Horizontal) return base.GetPreferredSize(constrainingSize); - Size result = base.GetPreferredSize(constrainingSize); + // with vertical orientation the image is too small + Size result = base.GetPreferredSize(constrainingSize); return new Size(result.Width + Owner.ScaleWidth(2), result.Height); } diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 41a96f2..f7fb455 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -71,7 +71,10 @@ protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) { Graphics g = e.Graphics; Rectangle dropDownRect = e.Item is ScalingToolStripDropDownButton scalingButton ? scalingButton.ArrowRectangle : e.ArrowRectangle; - using (Brush brush = new SolidBrush(e.Item.Enabled ? e.ArrowColor : SystemColors.ControlDark)) + Color color = !e.Item.Enabled ? SystemColors.ControlDark + : SystemInformation.HighContrast && e.Item.Selected && !e.Item.Pressed ? SystemColors.HighlightText + : e.ArrowColor; + using (Brush brush = new SolidBrush(color)) { Point middle = new Point(dropDownRect.Left + dropDownRect.Width / 2, dropDownRect.Top + dropDownRect.Height / 2); @@ -159,24 +162,39 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr if (btn.Enabled && (btn.Checked || !btn.DropDownButtonPressed)) { - rect.Inflate(-1, -1); - if (btn.ButtonPressed) - ClearImageBackground(e.Graphics, rect, ColorTable.ButtonPressedHighlight); - else if (btn.Selected) - ClearImageBackground(e.Graphics, rect, btn.Checked ? ColorTable.ButtonPressedHighlight : ColorTable.ButtonSelectedGradientMiddle); - else if (btn.Checked) - ClearImageBackground(e.Graphics, rect, ColorTable.ButtonSelectedGradientMiddle); - rect.Inflate(1, 1); + if (OSUtils.IsMono) + rect.Inflate(-1, -1); + Color color = SystemInformation.HighContrast ? btn.Selected || btn.Checked ? SystemColors.Highlight : Color.Empty + : btn.ButtonPressed ? ColorTable.ButtonPressedHighlight + : btn.Selected ? btn.Checked ? ColorTable.ButtonPressedHighlight : ColorTable.ButtonSelectedGradientMiddle + : btn.Checked ? ColorTable.ButtonSelectedGradientMiddle + : Color.Empty; + if (color != Color.Empty) + ClearImageBackground(e.Graphics, rect, color); + if (OSUtils.IsMono) + rect.Inflate(1, 1); } - // drawing border (maybe again, because it can be overridden by background) - if (btn.Checked || !btn.DropDownButtonPressed && (btn.ButtonPressed || btn.ButtonSelected)) + // drawing border (maybe again, because it can be overdrawn by the background) + bool drawBorder = btn.Checked || (!btn.DropDownButtonPressed && SystemInformation.HighContrast + ? btn.ButtonSelected && !btn.ButtonPressed // in high contrast mode no border is drawn when pressing the unchecked button + : btn.ButtonPressed || btn.ButtonSelected); + + if (drawBorder) { - using (Pen pen = new Pen(ColorTable.ButtonSelectedBorder)) + using (Pen pen = new Pen(SystemInformation.HighContrast ? SystemColors.ControlLight : ColorTable.ButtonSelectedBorder)) e.Graphics.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height - 1); } + +#if NETFRAMEWORK + // In high contrast mode the base does not call our overridden OnRenderArrow so both the size and the color might be incorrect + // Though it is not called in .NET Core either, the base paints the arrow correctly in .NET Core. + if (SystemInformation.HighContrast) + OnRenderArrow(new ToolStripArrowRenderEventArgs(e.Graphics, btn, btn.DropDownButtonBounds, SystemColors.ControlText, ArrowDirection.Down)); +#endif } + protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) { // Fixing image scaling in menu items on Mono @@ -194,6 +212,12 @@ protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) e.Graphics.DrawRectangle(pen, rect.X - 1, rect.Y - 1, rect.Width + 1, rect.Height + 1); } } + // In high contrast mode shifting the pressed button by 1 pixel just like in case of other buttons + else if (SystemInformation.HighContrast && e.Item is ToolStripSplitButton { ButtonPressed: true }) + { + e = new ToolStripItemImageRenderEventArgs(e.Graphics, e.Item, e.Image, + new Rectangle(e.ImageRectangle.X + 1, e.ImageRectangle.Y, e.ImageRectangle.Width, e.ImageRectangle.Height)); + } base.OnRenderItemImage(e); } From fed4a09c8eb8572545786a123609991e2ec04531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 26 Jun 2021 17:56:14 +0200 Subject: [PATCH 164/211] Graphics visualizer: removing unnecessary code --- .../ViewModel/GraphicsVisualizerViewModel.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/GraphicsVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/GraphicsVisualizerViewModel.cs index 6bf5126..449683d 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/GraphicsVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/GraphicsVisualizerViewModel.cs @@ -66,12 +66,6 @@ protected override void OnPropertyChanged(PropertyChangedExtendedEventArgs e) UpdateImageAndCommands(); } - internal override void ViewLoaded() - { - UpdateGraphicImage(); - base.ViewLoaded(); - } - protected override void UpdateInfo() { GraphicsInfo? graphicsInfo = GraphicsInfo; @@ -143,8 +137,6 @@ private void UpdateImageAndCommands() private void UpdateGraphicImage() { - if (!IsViewLoaded) - return; GraphicsInfo? graphicsInfo = GraphicsInfo; Bitmap? backingImage = graphicsInfo?.GraphicsImage; if (backingImage == null) From 922db47a8564afc2e35afa4853d1168bc745567a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 26 Jun 2021 20:19:39 +0200 Subject: [PATCH 165/211] ToolsTripButton: fixing checked appearance in high contrast mode --- .../View/Controls/AdvancedToolStrip.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index f7fb455..54bed66 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -141,7 +141,19 @@ protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e) { - if (e.Item is ToolStripButton { Checked: true, Enabled: true } btn) + var btn = (ToolStripButton)e.Item; + +#if NETFRAMEWORK + // In .NET Framework fixing the appearance in high contrast mode: not drawing the border if the button is selected and checked (this is the .NET Core behavior) + if (SystemInformation.HighContrast && btn.Enabled && (btn.Pressed || btn.Selected && btn.Checked)) + { + ClearImageBackground(e.Graphics, btn.ContentRectangle, SystemColors.Highlight); + return; + } +#endif + + // if button is checked we draw a middle gradient background (if the button is pressed or selected, this can be overdrawn by base) + if (!SystemInformation.HighContrast && btn.Checked && btn.Enabled) ClearImageBackground(e.Graphics, btn.ContentRectangle, ColorTable.ButtonSelectedGradientMiddle); base.OnRenderButtonBackground(e); @@ -176,9 +188,9 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr } // drawing border (maybe again, because it can be overdrawn by the background) - bool drawBorder = btn.Checked || (!btn.DropDownButtonPressed && SystemInformation.HighContrast - ? btn.ButtonSelected && !btn.ButtonPressed // in high contrast mode no border is drawn when pressing the unchecked button - : btn.ButtonPressed || btn.ButtonSelected); + bool drawBorder = !btn.DropDownButtonPressed && (SystemInformation.HighContrast + ? !btn.ButtonPressed && (btn.Checked ^ btn.ButtonSelected) // this is how simple button is also rendered in high contrast mode (starting with .NET Core) + : btn.Checked || btn.ButtonPressed || btn.ButtonSelected); if (drawBorder) { From d1eff558c0f2b5e221000101b1666b4d4d097baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sat, 26 Jun 2021 22:14:43 +0200 Subject: [PATCH 166/211] ToolStrip: improving appearance in high contrast mode --- .../View/Controls/AdvancedToolStrip.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 54bed66..72076a3 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -194,8 +194,24 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr if (drawBorder) { - using (Pen pen = new Pen(SystemInformation.HighContrast ? SystemColors.ControlLight : ColorTable.ButtonSelectedBorder)) - e.Graphics.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height - 1); + if (SystemInformation.HighContrast) + { + rect.Width += 1; + e.Graphics.DrawRectangle(SystemPens.ControlLight, rect.X, rect.Y, rect.Width, rect.Height - 1); + } + else + { + using (Pen pen = new Pen(ColorTable.ButtonSelectedBorder)) + e.Graphics.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height - 1); + } + } + + if (SystemInformation.HighContrast && !btn.DropDownButtonPressed && btn.Selected) + { + rect = btn.DropDownButtonBounds; + rect.Width -= 1; + rect.Height -= 1; + e.Graphics.DrawRectangle(SystemPens.ControlLight, rect); } #if NETFRAMEWORK From 5e17522d8fe9a2b92edc9f2c6cc33826c0f6751b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 27 Jun 2021 09:09:02 +0200 Subject: [PATCH 167/211] Fixing startup issue on Windows XP --- KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs index f047691..8363b70 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs @@ -107,8 +107,14 @@ static Configuration() typeof(CultureInfo).RegisterTypeConverter(); #endif #if NET35 || NET40 - // To be able to use HTTP requests with TLS 1.2 security protocol (may not work on Windows XP) - ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072; + try + { + // To be able to use HTTP requests with TLS 1.2 security protocol (may not work on Windows XP) + ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072; + } + catch (NotSupportedException) + { + } #elif NETFRAMEWORK // To be able to use HTTP requests with TLS 1.2 security protocol (may not work on Windows XP) ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; From 98a0932e9e9edee3c142850863873ee82a863e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Sun, 27 Jun 2021 17:23:10 +0200 Subject: [PATCH 168/211] Refactoring ScalingToolStripMenuRenderer --- .../View/Controls/AdvancedToolStrip.cs | 359 ++++++++++++------ .../View/_Extensions/ColorExtensions.cs | 63 +++ 2 files changed, 316 insertions(+), 106 deletions(-) create mode 100644 KGySoft.Drawing.ImagingTools/View/_Extensions/ColorExtensions.cs diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 72076a3..5dad99a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -33,16 +33,30 @@ namespace KGySoft.Drawing.ImagingTools.View.Controls /// /// A with some additional features: /// - It can scale its content regardless of .NET version and app.config settings. - /// - Custom renderer for checked state and scaled arrows. + /// - Custom renderer for corrected checked button appearance, scaled and correctly colored arrows, fixed high contrast appearance and more. /// - Tool tip supports right-to-left /// - Clicking works even if the owner form was not active /// internal class AdvancedToolStrip : ToolStrip { - #region ScalingToolStripMenuRenderer class + #region AdvancedToolStripRenderer class - private class ScalingToolStripMenuRenderer : ToolStripProfessionalRenderer + private class AdvancedToolStripRenderer : ToolStripProfessionalRenderer { + #region ButtonStyle enum + + [Flags] + private enum ButtonStyle : byte + { + None, + Selected = 1, + Pressed = 1 << 1, + Checked = 1 << 2, + Dropped = 1 << 3 + } + + #endregion + #region Fields private static readonly Size referenceOffset = new Size(2, 2); @@ -54,7 +68,7 @@ private class ScalingToolStripMenuRenderer : ToolStripProfessionalRenderer #region Static Methods - private static void ClearImageBackground(Graphics g, Rectangle rect, Color color) + private static void ClearButtonBackground(Graphics g, Rectangle rect, Color color) { GraphicsState state = g.Save(); rect.Inflate(1, 1); @@ -63,56 +77,149 @@ private static void ClearImageBackground(Graphics g, Rectangle rect, Color color g.Restore(state); } + private static void FillBackground(Graphics g, Rectangle rect, Color color1, Color color2) + { + if (color1.ToArgb() == color2.ToArgb()) + g.FillRectangle(color1.GetBrush(), rect); + else + { + using var brush = new LinearGradientBrush(rect, color1, color2, LinearGradientMode.Vertical); + g.FillRectangle(brush, rect); + } + } + + private static void DrawArrow(Graphics g, Color color, Rectangle bounds, ArrowDirection direction) + { + Point middle = new Point(bounds.Left + bounds.Width / 2, bounds.Top + bounds.Height / 2); + + Point[] arrow; + Size offset = g.ScaleSize(referenceOffset); + Size offsetDouble = g.ScaleSize(referenceOffsetDouble); + + switch (direction) + { + case ArrowDirection.Up: + arrow = new Point[] + { + new Point(middle.X - offset.Width, middle.Y + 1), + new Point(middle.X + offset.Width + 1, middle.Y + 1), + new Point(middle.X, middle.Y - offset.Height) + }; + break; + case ArrowDirection.Left: + arrow = new Point[] + { + new Point(middle.X + offset.Width, middle.Y - offsetDouble.Height), + new Point(middle.X + offset.Width, middle.Y + offsetDouble.Height), + new Point(middle.X - offset.Width, middle.Y) + }; + break; + case ArrowDirection.Right: + arrow = new Point[] + { + new Point(middle.X - offset.Width, middle.Y - offsetDouble.Height), + new Point(middle.X - offset.Width, middle.Y + offsetDouble.Height), + new Point(middle.X + offset.Width, middle.Y) + }; + break; + default: + arrow = new Point[] + { + new Point(middle.X - offset.Width, middle.Y - 1), + new Point(middle.X + offset.Width + 1, middle.Y - 1), + new Point(middle.X, middle.Y + offset.Height) + }; + break; + } + + g.FillPolygon(color.GetBrush(), arrow); + } + + private static void DrawThemedButtonBackground(Graphics g, ProfessionalColorTable colorTable, Rectangle bounds, ButtonStyle style) + { + #region Local Methods + + static void RenderWithVisualStyles(Graphics g, ProfessionalColorTable colorTable, Rectangle bounds, ButtonStyle style) + { + Color backgroundStart; + Color backgroundEnd; + Color border; + if ((style & ButtonStyle.Pressed) != 0 || (style & ButtonStyle.Selected) != 0 && (style & ButtonStyle.Checked) != 0) + { + backgroundStart = colorTable.ButtonPressedGradientBegin; + backgroundEnd = colorTable.ButtonPressedGradientEnd; + border = colorTable.ButtonPressedBorder; + } + else if ((style & ButtonStyle.Selected) != 0) + { + backgroundStart = colorTable.ButtonSelectedGradientBegin; + backgroundEnd = colorTable.ButtonSelectedGradientEnd; + border = colorTable.ButtonSelectedBorder; + } + else if ((style & ButtonStyle.Checked) != 0) + { + backgroundStart = colorTable.ButtonCheckedGradientBegin is { IsEmpty: false } c1 ? c1 : colorTable.ButtonCheckedHighlight; + backgroundEnd = colorTable.ButtonCheckedGradientEnd is { IsEmpty: false } c2 ? c2 : colorTable.ButtonCheckedHighlight; + border = colorTable.ButtonCheckedHighlightBorder; + } + else + return; + + FillBackground(g, bounds, backgroundStart, backgroundEnd); + g.DrawRectangle(border.GetPen(), bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1); + } + + static void RenderBasicTheme(Graphics g, ProfessionalColorTable colorTable, Rectangle bounds, ButtonStyle style) + { + Color backColor = (style & ButtonStyle.Pressed) != 0 || (style & ButtonStyle.Selected) != 0 && (style & ButtonStyle.Checked) != 0 ? colorTable.ButtonPressedHighlight + : (style & ButtonStyle.Selected) != 0 ? colorTable.ButtonSelectedHighlight + : (style & ButtonStyle.Checked) != 0 ? colorTable.ButtonCheckedHighlight + : Color.Empty; + g.FillRectangle(backColor.GetBrush(), bounds); + g.DrawRectangle(colorTable.ButtonSelectedBorder.GetPen(), bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1); + } + + #endregion + + if (style == ButtonStyle.None) + return; + if ((style & ButtonStyle.Dropped) != 0) + { + FillBackground(g, bounds, colorTable.MenuItemPressedGradientBegin, colorTable.MenuItemPressedGradientEnd); + g.DrawRectangle(colorTable.MenuBorder.GetPen(), bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1); + return; + } + + if (Application.RenderWithVisualStyles) + RenderWithVisualStyles(g, colorTable, bounds, style); + else + RenderBasicTheme(g, colorTable, bounds, style); + } + + private static void DrawHighContrastButtonBackground(Graphics g, Rectangle bounds, ButtonStyle style) + { + if ((style & ButtonStyle.Dropped) == 0 && (style & (ButtonStyle.Selected | ButtonStyle.Checked | ButtonStyle.Pressed)) != 0) + g.FillRectangle(SystemBrushes.Highlight, bounds); + + Color borderColor = (style & ButtonStyle.Dropped) != 0 ? SystemColors.ButtonHighlight + : (style & ButtonStyle.Pressed) == 0 && (style & (ButtonStyle.Checked | ButtonStyle.Selected)) is ButtonStyle.Checked or ButtonStyle.Selected ? SystemColors.ControlLight + : Color.Empty; + + if (!borderColor.IsEmpty) + g.DrawRectangle(borderColor.GetPen(), bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1); + } + #endregion #region Instance Methods protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) { - Graphics g = e.Graphics; Rectangle dropDownRect = e.Item is ScalingToolStripDropDownButton scalingButton ? scalingButton.ArrowRectangle : e.ArrowRectangle; Color color = !e.Item.Enabled ? SystemColors.ControlDark : SystemInformation.HighContrast && e.Item.Selected && !e.Item.Pressed ? SystemColors.HighlightText : e.ArrowColor; - using (Brush brush = new SolidBrush(color)) - { - Point middle = new Point(dropDownRect.Left + dropDownRect.Width / 2, dropDownRect.Top + dropDownRect.Height / 2); - - Point[] arrow; - - var offset = g.ScaleSize(referenceOffset); - var offsetDouble = g.ScaleSize(referenceOffsetDouble); - - switch (e.Direction) - { - case ArrowDirection.Up: - arrow = new Point[] { - new Point(middle.X - offset.Width, middle.Y + 1), - new Point(middle.X + offset.Width + 1, middle.Y + 1), - new Point(middle.X, middle.Y - offset.Height)}; - break; - case ArrowDirection.Left: - arrow = new Point[] { - new Point(middle.X + offset.Width, middle.Y - offsetDouble.Height), - new Point(middle.X + offset.Width, middle.Y + offsetDouble.Height), - new Point(middle.X - offset.Width, middle.Y)}; - break; - case ArrowDirection.Right: - arrow = new Point[] { - new Point(middle.X - offset.Width, middle.Y - offsetDouble.Height), - new Point(middle.X - offset.Width, middle.Y + offsetDouble.Height), - new Point(middle.X + offset.Width, middle.Y)}; - break; - default: - arrow = new Point[] { - new Point(middle.X - offset.Width, middle.Y - 1), - new Point(middle.X + offset.Width + 1, middle.Y - 1), - new Point(middle.X, middle.Y + offset.Height) }; - break; - } - - g.FillPolygon(brush, arrow); - } + DrawArrow(e.Graphics, color, dropDownRect, e.Direction); } protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) @@ -139,87 +246,127 @@ protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) image.Dispose(); } + /// + /// Changes to original: + /// - Background image is omitted + /// - Not selected checked background uses fallback color if current theme has transparent checked background + /// - [HighContrast]: Not drawing border if button is pressed and checked (this is how the .NET Core version also works) + /// protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e) { - var btn = (ToolStripButton)e.Item; + ToolStripButton button = (ToolStripButton)e.Item; + Rectangle bounds = new Rectangle(Point.Empty, button.Size); + ButtonStyle style = (button.Pressed ? ButtonStyle.Pressed : 0) + | (button.Checked ? ButtonStyle.Checked : 0) + | (button.Selected ? ButtonStyle.Selected : 0); -#if NETFRAMEWORK - // In .NET Framework fixing the appearance in high contrast mode: not drawing the border if the button is selected and checked (this is the .NET Core behavior) - if (SystemInformation.HighContrast && btn.Enabled && (btn.Pressed || btn.Selected && btn.Checked)) - { - ClearImageBackground(e.Graphics, btn.ContentRectangle, SystemColors.Highlight); - return; - } -#endif - - // if button is checked we draw a middle gradient background (if the button is pressed or selected, this can be overdrawn by base) - if (!SystemInformation.HighContrast && btn.Checked && btn.Enabled) - ClearImageBackground(e.Graphics, btn.ContentRectangle, ColorTable.ButtonSelectedGradientMiddle); + if (SystemInformation.HighContrast) + DrawHighContrastButtonBackground(e.Graphics, bounds, style); + else if (button.Enabled && style != ButtonStyle.None) + DrawThemedButtonBackground(e.Graphics, ColorTable, bounds, style); + else if (button.Owner != null && button.BackColor != button.Owner.BackColor) + e.Graphics.FillRectangle(button.BackColor.GetBrush(), bounds); + } - base.OnRenderButtonBackground(e); + protected override void OnRenderDropDownButtonBackground(ToolStripItemRenderEventArgs e) + { + base.OnRenderDropDownButtonBackground(e); } protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventArgs e) { - base.OnRenderSplitButtonBackground(e); - if (e.Item is not AdvancedToolStripSplitButton btn) - return; + #region Local Methods - // overriding background to behave the same way as ToolStripButton - Rectangle rect = btn.ButtonBounds; - if (OSUtils.IsMono) - rect.Location = Point.Empty; - else if (e.Item.RightToLeft == RightToLeft.Yes) - rect.Offset(-1, 0); - - if (btn.Enabled && (btn.Checked || !btn.DropDownButtonPressed)) + // Changes to original: + // - Background image is omitted + // - Separator width is ignored + // - Supporting AdvancedToolStripSplitButton checked state (rendering the same way as OnRenderButtonBackground does it) + static void DrawThemed(ToolStripItemRenderEventArgs e, ProfessionalColorTable colorTable, ButtonStyle style) { - if (OSUtils.IsMono) - rect.Inflate(-1, -1); - Color color = SystemInformation.HighContrast ? btn.Selected || btn.Checked ? SystemColors.Highlight : Color.Empty - : btn.ButtonPressed ? ColorTable.ButtonPressedHighlight - : btn.Selected ? btn.Checked ? ColorTable.ButtonPressedHighlight : ColorTable.ButtonSelectedGradientMiddle - : btn.Checked ? ColorTable.ButtonSelectedGradientMiddle - : Color.Empty; - if (color != Color.Empty) - ClearImageBackground(e.Graphics, rect, color); - if (OSUtils.IsMono) - rect.Inflate(1, 1); - } + AdvancedToolStripSplitButton button = (AdvancedToolStripSplitButton)e.Item; + Rectangle bounds = new Rectangle(Point.Empty, button.Size); + + // common part + ButtonStyle commonStyle = style & (ButtonStyle.Dropped | ButtonStyle.Selected); + if (commonStyle != ButtonStyle.None) + DrawThemedButtonBackground(e.Graphics, colorTable, bounds, commonStyle); + else if (button.Owner != null && button.BackColor != button.Owner.BackColor) + e.Graphics.FillRectangle(button.BackColor.GetBrush(), bounds); + + // button part + if ((style & ButtonStyle.Pressed) != 0 + || (style & ButtonStyle.Checked) != 0 + || (style & ButtonStyle.Selected) != 0 && (style & ButtonStyle.Dropped) == 0) + { + style &= ~ButtonStyle.Dropped; + bounds = button.ButtonBounds; + bounds.Width += 1; + if (button.RightToLeft == RightToLeft.Yes) + bounds.X -= 1; - // drawing border (maybe again, because it can be overdrawn by the background) - bool drawBorder = !btn.DropDownButtonPressed && (SystemInformation.HighContrast - ? !btn.ButtonPressed && (btn.Checked ^ btn.ButtonSelected) // this is how simple button is also rendered in high contrast mode (starting with .NET Core) - : btn.Checked || btn.ButtonPressed || btn.ButtonSelected); + DrawThemedButtonBackground(e.Graphics, colorTable, bounds, style); + } + + // arrow + DrawArrow(e.Graphics, button.Enabled ? SystemColors.ControlText : SystemColors.ControlDark, button.DropDownButtonBounds, ArrowDirection.Down); + } - if (drawBorder) + // Changes to original: + // - Fixed arrow color + // - Fixed border color when button is not dropped + // - Supporting AdvancedToolStripSplitButton checked state (rendering the same way as OnRenderButtonBackground does it) + static void DrawHighContrast(ToolStripItemRenderEventArgs e, ButtonStyle style) { - if (SystemInformation.HighContrast) + AdvancedToolStripSplitButton button = (AdvancedToolStripSplitButton)e.Item; + Rectangle bounds = new Rectangle(Point.Empty, button.Size); + Rectangle dropBounds = button.DropDownButtonBounds; + + // common part + ButtonStyle commonStyle = style & (ButtonStyle.Dropped | ButtonStyle.Selected); + if (commonStyle != ButtonStyle.None) + DrawHighContrastButtonBackground(e.Graphics, bounds, commonStyle); + + // button part + if ((style & ButtonStyle.Pressed) != 0 + || (style & ButtonStyle.Checked) != 0 + || (style & ButtonStyle.Selected) != 0 && (style & ButtonStyle.Dropped) == 0) { - rect.Width += 1; - e.Graphics.DrawRectangle(SystemPens.ControlLight, rect.X, rect.Y, rect.Width, rect.Height - 1); + bounds = button.ButtonBounds; + bounds.Width += 2; + if (button.RightToLeft == RightToLeft.Yes) + bounds.X -= 2; + + DrawHighContrastButtonBackground(e.Graphics, bounds, style & ~ButtonStyle.Dropped); } - else + + // drop down border + Color arrowColor = SystemColors.ControlText; + if ((style & ButtonStyle.Dropped) == 0 && (style & ButtonStyle.Selected) != 0) { - using (Pen pen = new Pen(ColorTable.ButtonSelectedBorder)) - e.Graphics.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height - 1); + e.Graphics.DrawRectangle(SystemPens.ControlLight, dropBounds.X, dropBounds.Y, dropBounds.Width - 1, dropBounds.Height - 1); + arrowColor = SystemColors.HighlightText; } + + DrawArrow(e.Graphics, arrowColor, button.DropDownButtonBounds, ArrowDirection.Down); } - if (SystemInformation.HighContrast && !btn.DropDownButtonPressed && btn.Selected) + #endregion + + if (e.Item is not AdvancedToolStripSplitButton button) { - rect = btn.DropDownButtonBounds; - rect.Width -= 1; - rect.Height -= 1; - e.Graphics.DrawRectangle(SystemPens.ControlLight, rect); + base.OnRenderSplitButtonBackground(e); + return; } -#if NETFRAMEWORK - // In high contrast mode the base does not call our overridden OnRenderArrow so both the size and the color might be incorrect - // Though it is not called in .NET Core either, the base paints the arrow correctly in .NET Core. + ButtonStyle style = (button.DropDownButtonPressed ? ButtonStyle.Dropped : 0) + | (button.ButtonPressed ? ButtonStyle.Pressed : 0) + | (button.Checked ? ButtonStyle.Checked : 0) + | (button.Selected ? ButtonStyle.Selected : 0); + if (SystemInformation.HighContrast) - OnRenderArrow(new ToolStripArrowRenderEventArgs(e.Graphics, btn, btn.DropDownButtonBounds, SystemColors.ControlText, ArrowDirection.Down)); -#endif + DrawHighContrast(e, style); + else + DrawThemed(e, ColorTable, style); } @@ -235,7 +382,7 @@ protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) // Windows paints this in base but Linux/Mono does not if (mi.Checked) { - ClearImageBackground(e.Graphics, rect, mi.Selected ? ColorTable.ButtonPressedHighlight : ColorTable.ButtonSelectedGradientMiddle); + ClearButtonBackground(e.Graphics, rect, mi.Selected ? ColorTable.ButtonPressedHighlight : ColorTable.ButtonSelectedGradientMiddle); using (Pen pen = new Pen(ColorTable.ButtonSelectedBorder)) e.Graphics.DrawRectangle(pen, rect.X - 1, rect.Y - 1, rect.Width + 1, rect.Height + 1); } @@ -249,7 +396,7 @@ protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) base.OnRenderItemImage(e); } - + #endregion #endregion @@ -260,7 +407,7 @@ protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) #region Fields #region Static Fields - + private static readonly Size referenceSize = new Size(16, 16); #endregion @@ -281,7 +428,7 @@ protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) public AdvancedToolStrip() { ImageScalingSize = Size.Round(this.ScaleSize(referenceSize)); - Renderer = new ScalingToolStripMenuRenderer(); + Renderer = new AdvancedToolStripRenderer(); toolTip = Reflector.TryGetProperty(this, nameof(ToolTip), out object? result) ? (ToolTip)result! : Reflector.TryGetField(this, "tooltip_window", out result) ? (ToolTip)result! : null; diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/ColorExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/ColorExtensions.cs new file mode 100644 index 0000000..1374480 --- /dev/null +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/ColorExtensions.cs @@ -0,0 +1,63 @@ +#region Copyright + +/////////////////////////////////////////////////////////////////////////////// +// File: ColorExtensions.cs +/////////////////////////////////////////////////////////////////////////////// +// Copyright (C) KGy SOFT, 2005-2021 - All Rights Reserved +// +// You should have received a copy of the LICENSE file at the top-level +// directory of this distribution. If not, then this file is considered as +// an illegal copy. +// +// Unauthorized copying of this file, via any medium is strictly prohibited. +/////////////////////////////////////////////////////////////////////////////// + +#endregion + +#region Usings + +using System.Drawing; + +using KGySoft.Collections; + +#endregion + +namespace KGySoft.Drawing.ImagingTools.View +{ + internal static class ColorExtensions + { + #region Fields + + private static readonly Cache penCache = new(c => new Pen(Color.FromArgb(c)), 4) + { + DisposeDroppedValues = true, + EnsureCapacity = true, + }; + + private static readonly Cache brushCache = new(c => new SolidBrush(Color.FromArgb(c)), 4) + { + DisposeDroppedValues = true, + EnsureCapacity = true, + }; + + #endregion + + #region Methods + + internal static Pen GetPen(this Color color) + { + if (color.IsSystemColor) + return SystemPens.FromSystemColor(color); + return penCache[color.ToArgb()]; + } + + internal static Brush GetBrush(this Color color) + { + if (color.IsSystemColor) + return SystemBrushes.FromSystemColor(color); + return brushCache[color.ToArgb()]; + } + + #endregion + } +} \ No newline at end of file From 3e3595bced35bb63e64deda6c59901e0ac61aafc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 28 Jun 2021 13:33:40 +0200 Subject: [PATCH 169/211] ToolStripDropDownButton border color fix in high contrast mode --- .../View/Controls/AdvancedToolStrip.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 5dad99a..a1dfad5 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -268,9 +268,24 @@ protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e) e.Graphics.FillRectangle(button.BackColor.GetBrush(), bounds); } + /// + /// Changes to original: + /// - [HighContrast]: Dropped border color matches the menu border color + /// protected override void OnRenderDropDownButtonBackground(ToolStripItemRenderEventArgs e) { - base.OnRenderDropDownButtonBackground(e); + ToolStripDropDownButton button = (ToolStripDropDownButton)e.Item; + Rectangle bounds = new Rectangle(Point.Empty, button.Size); + ButtonStyle style = (button.Pressed && button.HasDropDownItems ? ButtonStyle.Dropped : 0) + | (button.Pressed ? ButtonStyle.Pressed : 0) + | (button.Selected ? ButtonStyle.Selected : 0); + + if (SystemInformation.HighContrast) + DrawHighContrastButtonBackground(e.Graphics, bounds, style); + else if (button.Enabled && style != ButtonStyle.None) + DrawThemedButtonBackground(e.Graphics, ColorTable, bounds, style); + else if (button.Owner != null && button.BackColor != button.Owner.BackColor) + e.Graphics.FillRectangle(button.BackColor.GetBrush(), bounds); } protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventArgs e) From 3db14b0146f7b24dd12b101bef496953cbd6b6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 28 Jun 2021 14:47:12 +0200 Subject: [PATCH 170/211] Applying improved rendering also for regular ToolStripSplitButton instances --- .../View/Controls/AdvancedToolStrip.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index a1dfad5..5b10777 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -298,7 +298,7 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr // - Supporting AdvancedToolStripSplitButton checked state (rendering the same way as OnRenderButtonBackground does it) static void DrawThemed(ToolStripItemRenderEventArgs e, ProfessionalColorTable colorTable, ButtonStyle style) { - AdvancedToolStripSplitButton button = (AdvancedToolStripSplitButton)e.Item; + var button = (ToolStripSplitButton)e.Item; Rectangle bounds = new Rectangle(Point.Empty, button.Size); // common part @@ -332,7 +332,7 @@ static void DrawThemed(ToolStripItemRenderEventArgs e, ProfessionalColorTable co // - Supporting AdvancedToolStripSplitButton checked state (rendering the same way as OnRenderButtonBackground does it) static void DrawHighContrast(ToolStripItemRenderEventArgs e, ButtonStyle style) { - AdvancedToolStripSplitButton button = (AdvancedToolStripSplitButton)e.Item; + var button = (ToolStripSplitButton)e.Item; Rectangle bounds = new Rectangle(Point.Empty, button.Size); Rectangle dropBounds = button.DropDownButtonBounds; @@ -367,7 +367,7 @@ static void DrawHighContrast(ToolStripItemRenderEventArgs e, ButtonStyle style) #endregion - if (e.Item is not AdvancedToolStripSplitButton button) + if (e.Item is not ToolStripSplitButton button) { base.OnRenderSplitButtonBackground(e); return; @@ -375,8 +375,8 @@ static void DrawHighContrast(ToolStripItemRenderEventArgs e, ButtonStyle style) ButtonStyle style = (button.DropDownButtonPressed ? ButtonStyle.Dropped : 0) | (button.ButtonPressed ? ButtonStyle.Pressed : 0) - | (button.Checked ? ButtonStyle.Checked : 0) - | (button.Selected ? ButtonStyle.Selected : 0); + | (button.Selected ? ButtonStyle.Selected : 0) + | (button is AdvancedToolStripSplitButton { Checked: true } ? ButtonStyle.Checked : 0); if (SystemInformation.HighContrast) DrawHighContrast(e, style); From 316d5670d5d8d5d2deff4205bff95ee2907d2b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 28 Jun 2021 17:29:09 +0200 Subject: [PATCH 171/211] AdvancedToolStrip: improving checked menu item appearance --- .../View/Controls/AdvancedToolStrip.cs | 92 ++++++++----------- 1 file changed, 40 insertions(+), 52 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 5b10777..ba131a5 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -21,6 +21,7 @@ using System.Drawing.Drawing2D; using System.Windows.Forms; +using KGySoft.Collections; using KGySoft.CoreLibraries; using KGySoft.Drawing.ImagingTools.View.Components; using KGySoft.Drawing.ImagingTools.WinApi; @@ -61,6 +62,7 @@ private enum ButtonStyle : byte private static readonly Size referenceOffset = new Size(2, 2); private static readonly Size referenceOffsetDouble = new Size(4, 4); + private static readonly Cache disabledImagesCache = new(CreateDisabledImage, 16) { DisposeDroppedValues = true }; #endregion @@ -68,15 +70,6 @@ private enum ButtonStyle : byte #region Static Methods - private static void ClearButtonBackground(Graphics g, Rectangle rect, Color color) - { - GraphicsState state = g.Save(); - rect.Inflate(1, 1); - g.SetClip(rect); - g.Clear(color); - g.Restore(state); - } - private static void FillBackground(Graphics g, Rectangle rect, Color color1, Color color2) { if (color1.ToArgb() == color2.ToArgb()) @@ -213,6 +206,11 @@ private static void DrawHighContrastButtonBackground(Graphics g, Rectangle bound #region Instance Methods + /// + /// Changes to original: + /// - Fixed color + /// - Fixed scaling + /// protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) { Rectangle dropDownRect = e.Item is ScalingToolStripDropDownButton scalingButton ? scalingButton.ArrowRectangle : e.ArrowRectangle; @@ -222,30 +220,6 @@ protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) DrawArrow(e.Graphics, color, dropDownRect, e.Direction); } - protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) - { - Rectangle imageRect = e.ImageRectangle; - Image image = e.Image; - if (imageRect == Rectangle.Empty || image == null) - return; - - bool disposeImage = false; - if (!e.Item.Enabled) - { - image = CreateDisabledImage(image); - disposeImage = true; - } - - // Draw the checkmark background (providing no image) - base.OnRenderItemCheck(new ToolStripItemImageRenderEventArgs(e.Graphics, e.Item, null, e.ImageRectangle)); - - // Draw the checkmark image scaled to the image rectangle - e.Graphics.DrawImage(image, imageRect, new Rectangle(Point.Empty, image.Size), GraphicsUnit.Pixel); - - if (disposeImage) - image.Dispose(); - } - /// /// Changes to original: /// - Background image is omitted @@ -384,32 +358,46 @@ static void DrawHighContrast(ToolStripItemRenderEventArgs e, ButtonStyle style) DrawThemed(e, ColorTable, style); } + /// + /// Changes to original: + /// - Not drawing the default (possibly unscaled) check image + /// - Drawing the check background also in high contrast mode + /// - When VisualStyles are enabled, using slightly different colors than the original + /// + protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) + { + int size = e.Item.Height; + Rectangle bounds = new Rectangle(e.Item.RightToLeft == RightToLeft.Yes ? e.Item.Width - size - 1 : 2, 0, size, size); // e.ImageRectangle; + if (SystemInformation.HighContrast) + DrawHighContrastButtonBackground(e.Graphics, bounds, ButtonStyle.Selected); + else + DrawThemedButtonBackground(e.Graphics, ColorTable, bounds, e.Item.Selected ? ButtonStyle.Pressed : ButtonStyle.Selected); + } + /// + /// Changes to original: + /// - Unlike Windows' base implementation, not drawing the checked menu item background again, which is already done by OnRenderItemCheck + /// - [Mono]: Scaling menu item images + /// - [HighContrast]: Shifting also clicked ToolStripSplitButton images just like in case of buttons + /// protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) { - // Fixing image scaling in menu items on Mono - if (OSUtils.IsMono && e.Item is ToolStripMenuItem mi) + Rectangle bounds = e.ImageRectangle; + switch (e.Item) { - Rectangle rect = e.ImageRectangle; - rect.Size = e.Item.Owner.ScaleSize(referenceSize); - e = new ToolStripItemImageRenderEventArgs(e.Graphics, e.Item, e.Image, rect); + // Fixing image scaling in menu items on Mono + case ToolStripMenuItem when OSUtils.IsMono: + bounds.Size = e.Item.Owner.ScaleSize(referenceSize); + break; - // Windows paints this in base but Linux/Mono does not - if (mi.Checked) - { - ClearButtonBackground(e.Graphics, rect, mi.Selected ? ColorTable.ButtonPressedHighlight : ColorTable.ButtonSelectedGradientMiddle); - using (Pen pen = new Pen(ColorTable.ButtonSelectedBorder)) - e.Graphics.DrawRectangle(pen, rect.X - 1, rect.Y - 1, rect.Width + 1, rect.Height + 1); - } - } - // In high contrast mode shifting the pressed button by 1 pixel just like in case of other buttons - else if (SystemInformation.HighContrast && e.Item is ToolStripSplitButton { ButtonPressed: true }) - { - e = new ToolStripItemImageRenderEventArgs(e.Graphics, e.Item, e.Image, - new Rectangle(e.ImageRectangle.X + 1, e.ImageRectangle.Y, e.ImageRectangle.Width, e.ImageRectangle.Height)); + // In high contrast mode shifting the pressed buttons by 1 pixel, including ToolStripSplitButton + case ToolStripButton { Pressed: true }: + case ToolStripSplitButton { ButtonPressed: true }: + bounds.X += 1; + break; } - base.OnRenderItemImage(e); + e.Graphics.DrawImage(e.Item.Enabled ? e.Image : disabledImagesCache[e.Image], bounds); } #endregion From a84e670caa41ef3d6dcb9cd0c7d333b99b6e293f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 28 Jun 2021 17:51:00 +0200 Subject: [PATCH 172/211] Fixing high DPI appearance on Mono --- .../View/Controls/AdvancedToolStrip.cs | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index ba131a5..4c66f41 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -289,15 +289,21 @@ static void DrawThemed(ToolStripItemRenderEventArgs e, ProfessionalColorTable co { style &= ~ButtonStyle.Dropped; bounds = button.ButtonBounds; - bounds.Width += 1; - if (button.RightToLeft == RightToLeft.Yes) - bounds.X -= 1; + if (OSUtils.IsMono) + bounds.Location = Point.Empty; + else + { + bounds.Width += 1; + if (button.RightToLeft == RightToLeft.Yes) + bounds.X -= 1; + } DrawThemedButtonBackground(e.Graphics, colorTable, bounds, style); } - // arrow - DrawArrow(e.Graphics, button.Enabled ? SystemColors.ControlText : SystemColors.ControlDark, button.DropDownButtonBounds, ArrowDirection.Down); + // arrow: on Mono the OnRenderArrow is called normally, on Windows we have to draw it explicitly + if (!OSUtils.IsMono) + DrawArrow(e.Graphics, button.Enabled ? SystemColors.ControlText : SystemColors.ControlDark, button.DropDownButtonBounds, ArrowDirection.Down); } // Changes to original: @@ -341,12 +347,7 @@ static void DrawHighContrast(ToolStripItemRenderEventArgs e, ButtonStyle style) #endregion - if (e.Item is not ToolStripSplitButton button) - { - base.OnRenderSplitButtonBackground(e); - return; - } - + var button = (ToolStripSplitButton)e.Item; ButtonStyle style = (button.DropDownButtonPressed ? ButtonStyle.Dropped : 0) | (button.ButtonPressed ? ButtonStyle.Pressed : 0) | (button.Selected ? ButtonStyle.Selected : 0) @@ -367,7 +368,7 @@ static void DrawHighContrast(ToolStripItemRenderEventArgs e, ButtonStyle style) protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) { int size = e.Item.Height; - Rectangle bounds = new Rectangle(e.Item.RightToLeft == RightToLeft.Yes ? e.Item.Width - size - 1 : 2, 0, size, size); // e.ImageRectangle; + Rectangle bounds = new Rectangle(e.Item.RightToLeft == RightToLeft.Yes ? e.Item.Width - size - 1 : OSUtils.IsMono ? 1 : 2, 0, size, size); if (SystemInformation.HighContrast) DrawHighContrastButtonBackground(e.Graphics, bounds, ButtonStyle.Selected); else @@ -378,24 +379,18 @@ protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) /// Changes to original: /// - Unlike Windows' base implementation, not drawing the checked menu item background again, which is already done by OnRenderItemCheck /// - [Mono]: Scaling menu item images - /// - [HighContrast]: Shifting also clicked ToolStripSplitButton images just like in case of buttons + /// - [HighContrast]: Shifting also clicked ToolStripSplitButton images just like for buttons /// protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) { Rectangle bounds = e.ImageRectangle; - switch (e.Item) - { - // Fixing image scaling in menu items on Mono - case ToolStripMenuItem when OSUtils.IsMono: - bounds.Size = e.Item.Owner.ScaleSize(referenceSize); - break; - // In high contrast mode shifting the pressed buttons by 1 pixel, including ToolStripSplitButton - case ToolStripButton { Pressed: true }: - case ToolStripSplitButton { ButtonPressed: true }: - bounds.X += 1; - break; - } + // Fixing image scaling in menu items on Mono + if (OSUtils.IsMono && e.Item is ToolStripMenuItem) + bounds.Size = e.Item.Owner.ScaleSize(referenceSize); + // In high contrast mode shifting the pressed buttons by 1 pixel, including ToolStripSplitButton + else if (SystemInformation.HighContrast && e.Item is ToolStripButton { Pressed: true } or ToolStripSplitButton { ButtonPressed: true }) + bounds.X += 1; e.Graphics.DrawImage(e.Item.Enabled ? e.Image : disabledImagesCache[e.Image], bounds); } From 8cc2a26590b3863a7658aa7e686f808774c848e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 28 Jun 2021 19:26:48 +0200 Subject: [PATCH 173/211] Fixing ToolStripDropDown button appearance with high DPI --- .../ScalingToolStripDropDownButton.cs | 26 ++++++++++++++++--- .../View/Controls/AdvancedToolStrip.cs | 12 +++++++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Components/ScalingToolStripDropDownButton.cs b/KGySoft.Drawing.ImagingTools/View/Components/ScalingToolStripDropDownButton.cs index bb670b2..e3c0174 100644 --- a/KGySoft.Drawing.ImagingTools/View/Components/ScalingToolStripDropDownButton.cs +++ b/KGySoft.Drawing.ImagingTools/View/Components/ScalingToolStripDropDownButton.cs @@ -64,7 +64,7 @@ internal Padding ArrowPadding { if (arrowPadding != Padding.Empty) return arrowPadding; - var scaled = Size.Round(Owner.ScaleSize(arrowPaddingUnscaled)); + Size scaled = Size.Round(Owner.ScaleSize(arrowPaddingUnscaled)); return arrowPadding = new Padding(scaled.Width, scaled.Height, scaled.Width, scaled.Height); } } @@ -73,8 +73,8 @@ internal Rectangle ArrowRectangle { get { - var padding = ArrowPadding; - var size = ArrowSize; + Padding padding = ArrowPadding; + Size size = ArrowSize; var bounds = new Rectangle(Point.Empty, Size); if (TextDirection == ToolStripTextDirection.Horizontal) { @@ -93,6 +93,8 @@ internal Rectangle ArrowRectangle #region Methods + #region Public Methods + public override Size GetPreferredSize(Size constrainingSize) { var showArrow = ShowDropDownArrow; @@ -108,6 +110,22 @@ public override Size GetPreferredSize(Size constrainingSize) return preferredSize; } - #endregion + #endregion + + #region Internal Methods + +#if NETFRAMEWORK + internal void AdjustImageRectangle(ref Rectangle imageBounds) + { + if (RightToLeft == RightToLeft.Yes) + imageBounds.X = Width - 2 - imageBounds.Width; + else + imageBounds.X = 2; + } +#endif + + #endregion + + #endregion } } diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 4c66f41..537e0a0 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -391,8 +391,16 @@ protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) // In high contrast mode shifting the pressed buttons by 1 pixel, including ToolStripSplitButton else if (SystemInformation.HighContrast && e.Item is ToolStripButton { Pressed: true } or ToolStripSplitButton { ButtonPressed: true }) bounds.X += 1; - - e.Graphics.DrawImage(e.Item.Enabled ? e.Image : disabledImagesCache[e.Image], bounds); +#if NETFRAMEWORK + else if (e.Item is ScalingToolStripDropDownButton btn) + btn.AdjustImageRectangle(ref bounds); +#endif + + Image image = e.Item.Enabled ? e.Image : disabledImagesCache[e.Image]; + if (e.Item.ImageScaling == ToolStripItemImageScaling.None) + e.Graphics.DrawImage(image, bounds, new Rectangle(Point.Empty, bounds.Size), GraphicsUnit.Pixel); + else + e.Graphics.DrawImage(image, bounds); } #endregion From 911270c434caecce35b8aef86f8a58f87cf8f92e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 28 Jun 2021 19:40:49 +0200 Subject: [PATCH 174/211] Fixing ToolStripSplitButton arrow placement on Mono in high DPI mode --- .../View/Controls/AdvancedToolStrip.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 537e0a0..cd8085a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -210,9 +210,12 @@ private static void DrawHighContrastButtonBackground(Graphics g, Rectangle bound /// Changes to original: /// - Fixed color /// - Fixed scaling + /// - [Mono]: Ignoring ToolStripSplitButton because it is painted along the button just like in the MS world. /// protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) { + if (e.Item is ToolStripSplitButton) + return; Rectangle dropDownRect = e.Item is ScalingToolStripDropDownButton scalingButton ? scalingButton.ArrowRectangle : e.ArrowRectangle; Color color = !e.Item.Enabled ? SystemColors.ControlDark : SystemInformation.HighContrast && e.Item.Selected && !e.Item.Pressed ? SystemColors.HighlightText @@ -301,9 +304,12 @@ static void DrawThemed(ToolStripItemRenderEventArgs e, ProfessionalColorTable co DrawThemedButtonBackground(e.Graphics, colorTable, bounds, style); } - // arrow: on Mono the OnRenderArrow is called normally, on Windows we have to draw it explicitly - if (!OSUtils.IsMono) - DrawArrow(e.Graphics, button.Enabled ? SystemColors.ControlText : SystemColors.ControlDark, button.DropDownButtonBounds, ArrowDirection.Down); + // arrow + bounds = button.DropDownButtonBounds; + if (OSUtils.IsMono) + bounds.X -= button.ButtonBounds.Left; + + DrawArrow(e.Graphics, button.Enabled ? SystemColors.ControlText : SystemColors.ControlDark, bounds, ArrowDirection.Down); } // Changes to original: From 59fdd7ef5b78505f1b40fb238eab01ea3dce4459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 28 Jun 2021 20:34:13 +0200 Subject: [PATCH 175/211] AdvancedToolStrip: Adjusting ToolStripSplitButton image placement --- .../View/Controls/AdvancedToolStrip.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index cd8085a..fd8dafa 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -272,6 +272,7 @@ protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventAr // Changes to original: // - Background image is omitted // - Separator width is ignored + // - The separator placement matches with high contrast mode. On 100% DPI this means 1 pixel shift so the image area is perfectly rectangular // - Supporting AdvancedToolStripSplitButton checked state (rendering the same way as OnRenderButtonBackground does it) static void DrawThemed(ToolStripItemRenderEventArgs e, ProfessionalColorTable colorTable, ButtonStyle style) { @@ -290,18 +291,14 @@ static void DrawThemed(ToolStripItemRenderEventArgs e, ProfessionalColorTable co || (style & ButtonStyle.Checked) != 0 || (style & ButtonStyle.Selected) != 0 && (style & ButtonStyle.Dropped) == 0) { - style &= ~ButtonStyle.Dropped; bounds = button.ButtonBounds; if (OSUtils.IsMono) bounds.Location = Point.Empty; - else - { - bounds.Width += 1; - if (button.RightToLeft == RightToLeft.Yes) - bounds.X -= 1; - } + bounds.Width += 2; + if (button.RightToLeft == RightToLeft.Yes) + bounds.X -= 2; - DrawThemedButtonBackground(e.Graphics, colorTable, bounds, style); + DrawThemedButtonBackground(e.Graphics, colorTable, bounds, style & ~ButtonStyle.Dropped); } // arrow @@ -348,6 +345,7 @@ static void DrawHighContrast(ToolStripItemRenderEventArgs e, ButtonStyle style) arrowColor = SystemColors.HighlightText; } + // arrow DrawArrow(e.Graphics, arrowColor, button.DropDownButtonBounds, ArrowDirection.Down); } @@ -402,6 +400,10 @@ protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) btn.AdjustImageRectangle(ref bounds); #endif + // On ToolStripSplitButtons the image originally is not quite centered + if (e.Item is ToolStripSplitButton) + bounds.X += e.Item.RightToLeft == RightToLeft.Yes ? -1 : 1; + Image image = e.Item.Enabled ? e.Image : disabledImagesCache[e.Image]; if (e.Item.ImageScaling == ToolStripItemImageScaling.None) e.Graphics.DrawImage(image, bounds, new Rectangle(Point.Empty, bounds.Size), GraphicsUnit.Pixel); From 9458d312c21adf96a87f4b2109d6831e9b4c3939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Mon, 28 Jun 2021 22:50:21 +0200 Subject: [PATCH 176/211] Fixing arrow size on Linux --- .../View/Controls/AdvancedToolStrip.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index fd8dafa..acd70ae 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -94,9 +94,9 @@ private static void DrawArrow(Graphics g, Color color, Rectangle bounds, ArrowDi case ArrowDirection.Up: arrow = new Point[] { - new Point(middle.X - offset.Width, middle.Y + 1), + new Point(middle.X - offset.Width - 1, middle.Y + 1), new Point(middle.X + offset.Width + 1, middle.Y + 1), - new Point(middle.X, middle.Y - offset.Height) + new Point(middle.X, middle.Y - offset.Height - 1) }; break; case ArrowDirection.Left: @@ -119,7 +119,7 @@ private static void DrawArrow(Graphics g, Color color, Rectangle bounds, ArrowDi arrow = new Point[] { new Point(middle.X - offset.Width, middle.Y - 1), - new Point(middle.X + offset.Width + 1, middle.Y - 1), + new Point(middle.X + offset.Width + (OSUtils.IsMono && OSUtils.IsLinux ? 2 : 1), middle.Y - 1), new Point(middle.X, middle.Y + offset.Height) }; break; From 7cb8016c8ba9b3edbe9933d594e987d9acc8493d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 29 Jun 2021 10:37:06 +0200 Subject: [PATCH 177/211] AdvancedToolStrip: fixing menu item colors in high contrast mode --- .../View/Controls/AdvancedToolStrip.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index acd70ae..15f28f7 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -218,11 +218,34 @@ protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) return; Rectangle dropDownRect = e.Item is ScalingToolStripDropDownButton scalingButton ? scalingButton.ArrowRectangle : e.ArrowRectangle; Color color = !e.Item.Enabled ? SystemColors.ControlDark - : SystemInformation.HighContrast && e.Item.Selected && !e.Item.Pressed ? SystemColors.HighlightText + : SystemInformation.HighContrast ? e.Item.Selected && !e.Item.Pressed ? SystemColors.HighlightText : e.ArrowColor + : e.Item is ToolStripDropDownItem ? SystemColors.ControlText : e.ArrowColor; + DrawArrow(e.Graphics, color, dropDownRect, e.Direction); } + /// + /// Changes to original: + /// - [HighContrast]: Not drawing the highlighted background if the menu item is disabled (this is already fixed in Core) + /// - [HighContrast]: Fixed bounds of highlight rectangle (it was good in .NET Framework but is wrong in Core) + /// + protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) + { + if (!SystemInformation.HighContrast || e.Item is not ToolStripMenuItem item) + { + base.OnRenderMenuItemBackground(e); + return; + } + + // Selected/pressed menu point in high contrast mode: drawing the background only if enabled + var bounds = new Rectangle(2, 0, item.Width - 3, item.Height); + if (item.Pressed || item.Selected && item.Enabled) + e.Graphics.FillRectangle(SystemBrushes.Highlight, bounds); + else if (item.Selected && !item.Enabled) + e.Graphics.DrawRectangle(SystemPens.Highlight, bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1); + } + /// /// Changes to original: /// - Background image is omitted @@ -387,6 +410,8 @@ protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) /// protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) { + if (e.Image == null) + return; Rectangle bounds = e.ImageRectangle; // Fixing image scaling in menu items on Mono From c4516eb68162f6c2513dcb42f4a0c53add2858e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 29 Jun 2021 11:11:26 +0200 Subject: [PATCH 178/211] AdvncedToolStrip: mixing menu item arrow position on Mono high DPI mode --- .../View/Controls/AdvancedToolStrip.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 15f28f7..cf66164 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -211,18 +211,21 @@ private static void DrawHighContrastButtonBackground(Graphics g, Rectangle bound /// - Fixed color /// - Fixed scaling /// - [Mono]: Ignoring ToolStripSplitButton because it is painted along the button just like in the MS world. + /// - [Mono]: Fixing menu item arrow position in high DPI mode /// protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) { if (e.Item is ToolStripSplitButton) return; - Rectangle dropDownRect = e.Item is ScalingToolStripDropDownButton scalingButton ? scalingButton.ArrowRectangle : e.ArrowRectangle; + Rectangle bounds = e.Item is ScalingToolStripDropDownButton scalingButton ? scalingButton.ArrowRectangle + : OSUtils.IsMono && e.Item is ToolStripMenuItem mi ? new Rectangle(e.ArrowRectangle.Left, 0, e.ArrowRectangle.Width, mi.Height) + : e.ArrowRectangle; Color color = !e.Item.Enabled ? SystemColors.ControlDark : SystemInformation.HighContrast ? e.Item.Selected && !e.Item.Pressed ? SystemColors.HighlightText : e.ArrowColor : e.Item is ToolStripDropDownItem ? SystemColors.ControlText : e.ArrowColor; - - DrawArrow(e.Graphics, color, dropDownRect, e.Direction); + + DrawArrow(e.Graphics, color, bounds, e.Direction); } /// From 3ad78b49da9b19d6352b5bd12145e9a936cee5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 29 Jun 2021 11:33:18 +0200 Subject: [PATCH 179/211] Adjusting cache capacities --- .../View/Controls/AdvancedToolStrip.cs | 2 +- .../View/_Extensions/ColorExtensions.cs | 18 ++++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index cf66164..ddb5e40 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -62,7 +62,7 @@ private enum ButtonStyle : byte private static readonly Size referenceOffset = new Size(2, 2); private static readonly Size referenceOffsetDouble = new Size(4, 4); - private static readonly Cache disabledImagesCache = new(CreateDisabledImage, 16) { DisposeDroppedValues = true }; + private static readonly Cache disabledImagesCache = new(CreateDisabledImage, 8) { DisposeDroppedValues = true }; #endregion diff --git a/KGySoft.Drawing.ImagingTools/View/_Extensions/ColorExtensions.cs b/KGySoft.Drawing.ImagingTools/View/_Extensions/ColorExtensions.cs index 1374480..3b4e96e 100644 --- a/KGySoft.Drawing.ImagingTools/View/_Extensions/ColorExtensions.cs +++ b/KGySoft.Drawing.ImagingTools/View/_Extensions/ColorExtensions.cs @@ -44,19 +44,13 @@ internal static class ColorExtensions #region Methods - internal static Pen GetPen(this Color color) - { - if (color.IsSystemColor) - return SystemPens.FromSystemColor(color); - return penCache[color.ToArgb()]; - } + internal static Pen GetPen(this Color color) => color.IsSystemColor + ? SystemPens.FromSystemColor(color) + : penCache[color.ToArgb()]; - internal static Brush GetBrush(this Color color) - { - if (color.IsSystemColor) - return SystemBrushes.FromSystemColor(color); - return brushCache[color.ToArgb()]; - } + internal static Brush GetBrush(this Color color) => color.IsSystemColor + ? SystemBrushes.FromSystemColor(color) + : brushCache[color.ToArgb()]; #endregion } From 45345a29d53c2e8f7c6a118a40bf3c915bddfdf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 29 Jun 2021 11:48:37 +0200 Subject: [PATCH 180/211] Updating change log --- changelog.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index 0ecef82..f52d1b5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -18,9 +18,11 @@ + Supporting localization from .resx files with editing, downloading and applying without restarting. Note: Right-to-left languages are also supported though with some limitations, especially under Linux/Mono. On Windows some validation tool tips are not aligned right but otherwise the layout is arranged correctly. -+ Palette visualizer form: OK/Cancel buttons -+ Color visualizer form: OK/Cancel buttons -+ Image visualizer form: OK/Cancel buttons +* Image visualizer form: + + OK/Cancel buttons in debug mode, non-read-only context + - Fixing several appearance tool strip appearance issues, including high DPI and high contrast modes ++ Palette visualizer form: OK/Cancel buttons in non-read-only context ++ Color visualizer form: OK/Cancel buttons in non-read-only context * Manual zooming is enabled even if Auto Zoom is on; this turns off Auto Zoom automatically. * ImageViewer control: * Turning on/off smoothing of zoomed images affects also shrunk images (previously affected enlarged images only). From 6669afbaabe394a2d0dfb7bec17f81894b4f658a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 29 Jun 2021 16:11:13 +0200 Subject: [PATCH 181/211] Allowing non-HTTPS download where it can be an issue (Windows XP, Mono on Windows) --- KGySoft.Drawing.ImagingTools/App.config | 2 +- .../_Classes/Configuration.cs | 24 ++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/App.config b/KGySoft.Drawing.ImagingTools/App.config index 2f81111..c76c674 100644 --- a/KGySoft.Drawing.ImagingTools/App.config +++ b/KGySoft.Drawing.ImagingTools/App.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs index 8363b70..1e0ad27 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs @@ -68,12 +68,14 @@ private sealed class CultureInfoConverterFixed : CultureInfoConverter #region Constants private const string defaultResourceRepositoryLocation = "https://koszeggy.github.io/KGySoft.Drawing.Tools/res/"; // same as "https://raw.githubusercontent.com/koszeggy/KGySoft.Drawing.Tools/pages/res/" + private const string fallbackResourceRepositoryLocation = "http://koszeggy.github.io/KGySoft.Drawing.Tools/res/"; #endregion #region Fields private static Uri? baseUri; + private static bool allowHttps; #endregion @@ -90,7 +92,8 @@ private sealed class CultureInfoConverterFixed : CultureInfoConverter #region Private Properties - private static string ResourceRepositoryLocation => GetFromAppConfig() ?? defaultResourceRepositoryLocation; + private static string ResourceRepositoryLocation => GetFromAppConfig() + ?? (allowHttps ? defaultResourceRepositoryLocation : fallbackResourceRepositoryLocation); #endregion @@ -100,24 +103,33 @@ private sealed class CultureInfoConverterFixed : CultureInfoConverter static Configuration() { + allowHttps = !(OSUtils.IsMono && OSUtils.IsWindows); + // To be able to resolve UserSettingsGroup of with other framework version AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; #if NET35 // To prevent serializing CultureInfo by DisplayName instead of Name typeof(CultureInfo).RegisterTypeConverter(); #endif -#if NET35 || NET40 + +#if NETFRAMEWORK + if (!allowHttps) + return; + try { // To be able to use HTTP requests with TLS 1.2 security protocol (may not work on Windows XP) - ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072; + ServicePointManager.SecurityProtocol |= +#if NET35 || NET40 + (SecurityProtocolType)3072; +#else + SecurityProtocolType.Tls12; +#endif } catch (NotSupportedException) { + allowHttps = false; } -#elif NETFRAMEWORK - // To be able to use HTTP requests with TLS 1.2 security protocol (may not work on Windows XP) - ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; #endif } From 069f2bc9b45eef79735afcaca73d43fc7af349d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 29 Jun 2021 16:40:34 +0200 Subject: [PATCH 182/211] Fixing alpha pattern on Mono --- .../UserControls/ColorVisualizerControl.cs | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs index d3d4f3e..08b1b17 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs @@ -19,6 +19,8 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; using System.Text; using System.Windows.Forms; @@ -43,7 +45,8 @@ internal partial class ColorVisualizerControl : BaseUserControl private bool readOnly; private Color color; - private TextureBrush? alphaBrush; + private Bitmap? alphaPattern; + private ImageAttributes? attrTiles; private string? specialInfo; #endregion @@ -211,10 +214,12 @@ protected override void Dispose(bool disposing) if (disposing && (components != null)) { components.Dispose(); - alphaBrush?.Dispose(); + alphaPattern?.Dispose(); + attrTiles?.Dispose(); } - alphaBrush = null; + alphaPattern = null; + attrTiles = null; pnlAlpha.Paint -= pnlColor_Paint; pnlColor.Paint -= pnlColor_Paint; btnSelectColor.Click -= btnEdit_Click; @@ -265,7 +270,7 @@ private void UpdateInfo() txtColor.Text = sb.ToString(); } - private void CreateAlphaBrush() + private void CreateAlphaPattern() { Size size = new Size(10, 10).Scale(this.GetScale()); var bmpPattern = new Bitmap(size.Width, size.Height); @@ -278,7 +283,10 @@ private void CreateAlphaBrush() g.FillRectangle(Brushes.Silver, smallRect); } - alphaBrush = new TextureBrush(bmpPattern); + // Using a TextureBrush would be simpler but that is not supported on Mono + attrTiles = new ImageAttributes(); + attrTiles.SetWrapMode(WrapMode.Tile); + alphaPattern = bmpPattern; } private void OnColorEdited() => Events.GetHandler(nameof(ColorEdited))?.Invoke(this, EventArgs.Empty); @@ -294,10 +302,11 @@ private void pnlColor_Paint(object sender, PaintEventArgs e) // painting checked background if (color.A != 255) { - if (alphaBrush == null) - CreateAlphaBrush(); + if (alphaPattern == null) + CreateAlphaPattern(); - e.Graphics.FillRectangle(alphaBrush!, e.ClipRectangle); + Size size = pnlColor.Size; + e.Graphics.DrawImage(alphaPattern, new Rectangle(Point.Empty, size), 0, 0 , size.Width, size.Height, GraphicsUnit.Pixel, attrTiles); } Color backColor = sender == pnlAlpha ? Color.FromArgb(color.A, Color.White) : color; From ef7df8aa45f8ccd83886c5980cd44f14585c37f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Tue, 29 Jun 2021 16:57:29 +0200 Subject: [PATCH 183/211] Updating changelog and readme --- .../readme.md | 18 ++++++++++++++---- changelog.txt | 17 ++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md b/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md index 0417ae7..2cdd15a 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md @@ -10,13 +10,17 @@ Either click the magnifier icon or choose a debugger visualizer from the drop do ![Debugging Graphics](https://kgysoft.net/images/DebugGraphics.png) -If an image or icon instance is debugged in a non read-only context, then it can be replaced and the palette entries of non read-only indexed bitmaps can be edited. +If an image or icon instance is debugged in a non read-only context, then it can be modified, replaced or cleared. + +![Changing pixel format with quantizing and dithering](https://kgysoft.net/images/Quantizing.png) + +Several modifications are allowed on non-read-only images such as rotating, resizing, changing pixel format with quantizing and dithering, adjusting brightness, contrast and gamma, or even editing the palette entries of indexed bitmaps. ![Debugging Palette](https://kgysoft.net/images/DebugPalette.png) ## Installing Debugger Visualizers -* For Visual Studio 2013 and above you can use this VSIX installer (tested with Visual Studio 2015, 2017, 2019). It will install the .NET 4.5 version. +* For Visual Studio 2013 and above you can use this VSIX package (tested with versions up to Visual Studio 2019). It will install the .NET 4.5 version. * For older Visual Studio versions and/or frameworks follow the [installation steps](https://github.com/koszeggy/KGySoft.Drawing.Tools#installing-debugger-visualizers) at the project site. ## Release Notes @@ -28,7 +32,7 @@ See the [change log](https://github.com/koszeggy/KGySoft.Drawing.Tools/blob/mast **Q:** Can I use the debugger visualizers for other Visual Studio versions?
**A:** The VSIX installer supports Visual Studio 2013 and newer versions (tested until 2019). However, you can install the debugger visualizers manually for any version starting with Visual Studio 2008. See the [installation steps](https://github.com/koszeggy/KGySoft.Drawing.Tools#installing-debugger-visualizers) at the project site. -**Q:** Is Visual Studio Core supported? +**Q:** Is Visual Studio Code supported?
**A:** As it has a completely different API, it is not supported yet. **Q:** I get an error message when I click the magnifier icon. @@ -37,8 +41,14 @@ See the [change log](https://github.com/koszeggy/KGySoft.Drawing.Tools/blob/mast **Q:** Are WPF image types supported?
**A:** No, these visualizers are for `System.Drawing` types. But the built-in Dependency Object visualizer is able to display image sources anyway. +**Q:** Where do I find the edited/downloaded resource files? Even my previously edited/downloaded resources have been disappeared. +
**A:** The language resources belong to the executed ImagingTools instance where they can be found in a `Resources` subfolder. The saved resources might be at different possible locations: +* If you execute a manually deployed version the resources will be in a `Resources` subfolder in the folder you executed the ImagingTools from +* During debugging the tool is executed from the debugger visualizers folder: `Documents\Visual Studio \Visualizers` +* If you launch the tool from the Visual Studio Tools menu, then it is located under `ProgramData\Microsoft\VisualStudio\Packages\...` + **Q:** I have removed the debugger visualizer extension, and it is still working. How can I remove it completely? -
**A:** When the extension is active it copies the visualizers into the `Visual Studio \Visualizers` folder if it is not there. Unlike an MSI installer the VSIX packages do not support uninstall actions so this copied content will not be removed automatically. However, the extension creates also a _KGy SOFT Drawing Debugger Visualizers/Manage Installations..._ menu item under the Tools menu where you can remove the installation from the Documents folder. So the proper way of a complete uninstall: +
**A:** When the extension is active it copies the visualizers into the `Documents\Visual Studio \Visualizers` folder if it is not there. Unlike an MSI installer the VSIX packages do not support uninstall actions so this copied content will not be removed automatically. However, the extension creates also a _KGy SOFT Drawing Debugger Visualizers/Manage Installations..._ menu item under the Tools menu where you can remove the installation from the Documents folder. So the proper way of a complete uninstall: 1. Click _Tools/Drawing Debugger Visualizers/Manage Installations..._ 2. Select the current Visual Studio version and click "Remove" 3. Now uninstall the extension from the _Tools/Extensions and Updates..._ (2019: _Extensions/Manage Extensions_) menu. Without this last step the debugger visualizers will be automatically reinstalled when you restart Visual Studio. diff --git a/changelog.txt b/changelog.txt index f52d1b5..903907d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -15,14 +15,16 @@ + Now panning a zoomed image is possible also by clicking and dragging with the mouse (besides usual scrolling). + Zooming is now possible also with keyboard shortcuts, the Auto Zoom button has now a drop-down part for the additional options. -+ Supporting localization from .resx files with editing, downloading and applying without restarting. - Note: Right-to-left languages are also supported though with some limitations, especially under Linux/Mono. ++ Supporting localization from .resx files with editing, downloading and applying changes on-the-fly. + Note: Right-to-left languages are also supported though with some limitations, especially under Mono. On Windows some validation tool tips are not aligned right but otherwise the layout is arranged correctly. * Image visualizer form: - + OK/Cancel buttons in debug mode, non-read-only context - - Fixing several appearance tool strip appearance issues, including high DPI and high contrast modes -+ Palette visualizer form: OK/Cancel buttons in non-read-only context -+ Color visualizer form: OK/Cancel buttons in non-read-only context + + OK/Cancel buttons in debug mode when image is editable + - Fixing several tool strip appearance issues, including high DPI and high contrast modes +* Palette visualizer form: + + OK/Cancel buttons when palette is editable + - Fixing the alpha pattern on Mono ++ Color visualizer form: OK/Cancel buttons then the color is editable * Manual zooming is enabled even if Auto Zoom is on; this turns off Auto Zoom automatically. * ImageViewer control: * Turning on/off smoothing of zoomed images affects also shrunk images (previously affected enlarged images only). @@ -35,6 +37,7 @@ - Manage Installations: - Remove was always enabled even it there was no installed version - Fixing possible errors when closing forms while an async operation is still in progress. + * API changes: * Members are annotated for using C# 8.0 nullable references + InstallationManager class: @@ -62,7 +65,7 @@ * KGySoft.Drawing.DebuggerVisualizers.Package.dll ================================================= -* ImagingTools and Manage Installations installations in Visual Studio Tools menu are executed on a new thread +* ImagingTools and Manage Installations executed from the Visual Studio Tools menu are executed on a new thread so the possible lagging of Visual Studio does not affect the performance anymore. From 433d084899d845b0b6b37f4ebf464b2f87d79f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 30 Jun 2021 11:17:05 +0200 Subject: [PATCH 184/211] Fixing code analyzer issues --- .../View/UserControls/ColorVisualizerControl.cs | 2 +- KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs index 08b1b17..fe61e96 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.cs @@ -306,7 +306,7 @@ private void pnlColor_Paint(object sender, PaintEventArgs e) CreateAlphaPattern(); Size size = pnlColor.Size; - e.Graphics.DrawImage(alphaPattern, new Rectangle(Point.Empty, size), 0, 0 , size.Width, size.Height, GraphicsUnit.Pixel, attrTiles); + e.Graphics.DrawImage(alphaPattern!, new Rectangle(Point.Empty, size), 0, 0 , size.Width, size.Height, GraphicsUnit.Pixel, attrTiles); } Color backColor = sender == pnlAlpha ? Color.FromArgb(color.A, Color.White) : color; diff --git a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs index 1e0ad27..98ad5a3 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs @@ -74,8 +74,9 @@ private sealed class CultureInfoConverterFixed : CultureInfoConverter #region Fields + private static readonly bool allowHttps; + private static Uri? baseUri; - private static bool allowHttps; #endregion @@ -190,7 +191,7 @@ private static void SetInSettings(object value, [CallerMemberName]string propert if (args.Name.StartsWith("System, Version=", StringComparison.Ordinal)) return typeof(UserSettingsGroup).Assembly; #elif NETCOREAPP - if (args.Name?.StartsWith("System.Configuration.ConfigurationManager, Version=", StringComparison.Ordinal) == true) + if (args.Name.StartsWith("System.Configuration.ConfigurationManager, Version=", StringComparison.Ordinal)) return typeof(UserSettingsGroup).Assembly; #endif return null; From 9c80554cfd2e8c972a81c5f1269dd444b37d8a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 30 Jun 2021 11:17:40 +0200 Subject: [PATCH 185/211] If executing as an app, enabling smooth zooming by default --- .../ViewModel/DefaultViewModel.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DefaultViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DefaultViewModel.cs index b5475d0..c9cfd84 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DefaultViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DefaultViewModel.cs @@ -44,6 +44,12 @@ internal class DefaultViewModel : ImageVisualizerViewModel #endregion + #region Constructors + + internal DefaultViewModel() => SmoothZooming = true; + + #endregion + #region Methods #region Internal Methods @@ -51,10 +57,8 @@ internal class DefaultViewModel : ImageVisualizerViewModel internal override void ViewLoaded() { string[]? args = CommandLineArguments; - if (args.IsNullOrEmpty()) + if (args.IsNullOrEmpty() || !ProcessArgs(args!)) UpdateInfo(); - else - ProcessArgs(args!); base.ViewLoaded(); } @@ -125,15 +129,18 @@ protected override void Clear() #region Private Methods - private void ProcessArgs(string[] args) + private bool ProcessArgs(string[] args) { if (args.Length == 0) - return; + return false; string file = args[0]; if (!File.Exists(file)) + { ShowError(Res.ErrorMessageFileDoesNotExist(file)); - else - OpenFile(file); + return false; + } + + return OpenFile(file); } #endregion From 3a2fddcf3352abbaa27c3001a7edb1ccae452eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 30 Jun 2021 12:41:50 +0200 Subject: [PATCH 186/211] Color visualizer: track bars could have different sizes in high DPI mode --- .../ColorVisualizerControl.Designer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs index 0d63f87..14b2c28 100644 --- a/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/UserControls/ColorVisualizerControl.Designer.cs @@ -108,7 +108,8 @@ private void InitializeComponent() // // tbRed // - this.tbRed.Dock = System.Windows.Forms.DockStyle.Fill; + this.tbRed.AutoSize = false; + this.tbRed.Dock = System.Windows.Forms.DockStyle.Top; this.tbRed.LargeChange = 64; this.tbRed.Location = new System.Drawing.Point(0, 0); this.tbRed.Maximum = 255; @@ -141,7 +142,8 @@ private void InitializeComponent() // // tbGreen // - this.tbGreen.Dock = System.Windows.Forms.DockStyle.Fill; + this.tbGreen.AutoSize = false; + this.tbGreen.Dock = System.Windows.Forms.DockStyle.Top; this.tbGreen.LargeChange = 64; this.tbGreen.Location = new System.Drawing.Point(0, 0); this.tbGreen.Maximum = 255; @@ -174,7 +176,8 @@ private void InitializeComponent() // // tbBlue // - this.tbBlue.Dock = System.Windows.Forms.DockStyle.Fill; + this.tbBlue.AutoSize = false; + this.tbBlue.Dock = System.Windows.Forms.DockStyle.Top; this.tbBlue.LargeChange = 64; this.tbBlue.Location = new System.Drawing.Point(0, 0); this.tbBlue.Maximum = 255; @@ -218,7 +221,8 @@ private void InitializeComponent() // // tbAlpha // - this.tbAlpha.Dock = System.Windows.Forms.DockStyle.Fill; + this.tbAlpha.AutoSize = false; + this.tbAlpha.Dock = System.Windows.Forms.DockStyle.Top; this.tbAlpha.LargeChange = 64; this.tbAlpha.Location = new System.Drawing.Point(0, 0); this.tbAlpha.Maximum = 255; @@ -272,16 +276,12 @@ private void InitializeComponent() this.tblColor.ResumeLayout(false); this.tblColor.PerformLayout(); this.pnlRed.ResumeLayout(false); - this.pnlRed.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.tbRed)).EndInit(); this.pnlGreen.ResumeLayout(false); - this.pnlGreen.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.tbGreen)).EndInit(); this.pnlBlue.ResumeLayout(false); - this.pnlBlue.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.tbBlue)).EndInit(); this.pnlAlpha.ResumeLayout(false); - this.pnlAlpha.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.tbAlpha)).EndInit(); this.tsMenu.ResumeLayout(false); this.tsMenu.PerformLayout(); From 6e501a9156e3bdd39ca254461a47df4771b162c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 30 Jun 2021 14:31:55 +0200 Subject: [PATCH 187/211] Fixing check box position of CheckGroupBox when resizing in right-to-left mode --- .../View/Controls/CheckGroupBox.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs index 6f764bf..2cc6ebd 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/CheckGroupBox.cs @@ -135,6 +135,13 @@ protected override void OnControlAdded(ControlEventArgs e) protected virtual void OnCheckedChanged(EventArgs e) => (Events[nameof(CheckedChanged)] as EventHandler)?.Invoke(this, e); + protected override void OnSizeChanged(EventArgs e) + { + base.OnSizeChanged(e); + if (RightToLeft == RightToLeft.Yes) + ResetCheckBoxLocation(); + } + protected override void OnRightToLeftChanged(EventArgs e) { base.OnRightToLeftChanged(e); From 8038790d0f1806535d294f7e28b3ec4b29dc034c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 30 Jun 2021 15:10:01 +0200 Subject: [PATCH 188/211] PalettePanel: On very high DPIs the scrollbar might have to be displayed even before any resizing --- .../View/Controls/PalettePanel.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs index fb4f607..cfb2fb9 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/PalettePanel.cs @@ -190,7 +190,13 @@ protected override void Dispose(bool disposing) protected override void OnPaint(PaintEventArgs e) { - scale = e.Graphics.GetScale(); + PointF currentScale = e.Graphics.GetScale(); + if (currentScale != scale) + { + scale = currentScale; + ResetLayout(); + } + base.OnPaint(e); if (ColorCount == 0) return; From 96f9b3e685558041f697ec28823e4d8d684efdf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 30 Jun 2021 16:02:27 +0200 Subject: [PATCH 189/211] Fixing scaling check box size calculation in .NET 3.5, very high DPI, high contrast mode --- .../View/Controls/ScalingCheckBox.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingCheckBox.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingCheckBox.cs index b7bd8ca..c6c745a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingCheckBox.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingCheckBox.cs @@ -42,8 +42,12 @@ public override Size GetPreferredSize(Size proposedSize) // The gap between the CheckBox and the text is 3px smaller with System at every DPI Size result = base.GetPreferredSize(proposedSize); -#if !NET35 - // The scaling is different in .NET 3.5 so there we don't subtract the padding difference +#if NET35 + // The scaling is different in .NET 3.5 so instead if subtracting a constant padding difference + // we need to add some based on scaling, but only in high contrast mode + if (SystemInformation.HighContrast) + result.Width += this.ScaleWidth(2); +#else result.Width -= 3; #endif From f6b23029f1ab402360edeb688c9011805b088fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 30 Jun 2021 16:40:54 +0200 Subject: [PATCH 190/211] .NET 3.5 high contrast mode: fixing text color on highlighted menu items --- .../View/Controls/AdvancedToolStrip.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index ddb5e40..30cc75a 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -249,6 +249,24 @@ protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e.Graphics.DrawRectangle(SystemPens.Highlight, bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1); } +#if NET35 + /// + /// Changes to original: + /// - [HighContrast]: Fixing text color on highlighted menu items + /// + protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) + { + if (SystemInformation.HighContrast && e.Item is ToolStripMenuItem mi) + { + e.TextColor = !mi.Enabled ? SystemColors.GrayText + : mi.Selected || mi.Pressed ? SystemColors.HighlightText + : SystemColors.ControlText; + } + + base.OnRenderItemText(e); + } +#endif + /// /// Changes to original: /// - Background image is omitted From 3d0dcfd2fc8832e880323f64e9f8fa540ed9c3e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 30 Jun 2021 19:51:52 +0200 Subject: [PATCH 191/211] Fixing resource downloading while debugging in VS2015 in Windows 7 --- .../Model/LocalizationInfo.cs | 38 ++++++++++++++++++- .../ViewModel/DownloadResourcesViewModel.cs | 3 +- .../ViewModel/DownloadableResourceItem.cs | 2 +- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs b/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs index 26a5409..1a6e783 100644 --- a/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs +++ b/KGySoft.Drawing.ImagingTools/Model/LocalizationInfo.cs @@ -31,8 +31,16 @@ namespace KGySoft.Drawing.ImagingTools.Model [SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "ReSharper issue")] public class LocalizationInfo { + #region Fields + + private Version? version; + + #endregion + #region Properties + #region Public Properties + /// /// Gets or sets the culture name of the localization that matches the property of the represented culture. /// @@ -46,7 +54,7 @@ public class LocalizationInfo /// /// Gets or sets the version number of the KGySoft.Drawing.ImagingTools.exe assembly this localization belongs to. /// - public Version ImagingToolsVersion { get; set; } = default!; + public string ImagingToolsVersion { get; set; } = default!; /// /// Gets or sets the author of the localization. @@ -59,5 +67,33 @@ public class LocalizationInfo public LocalizableLibraries ResourceSets { get; set; } = default!; #endregion + + #region Internal Properties + + // Note: Not a public property because XML deserialization may fail in very special circumstances + // (eg. executing as debugger visualizer in Windows 7 with VS2015). + internal Version Version + { + get + { + if (version is null) + { + try + { + version = new Version(ImagingToolsVersion); + } + catch (Exception e) when (!e.IsCritical()) + { + version = new Version(); + } + } + + return version; + } + } + + #endregion + + #endregion } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs index 97fbaef..1510d73 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadResourcesViewModel.cs @@ -192,7 +192,6 @@ private void DoDownloadManifest(object state) if (task.IsCanceled) return; - Progress = (2, 0); byte[]? data = Download(task); if (data == null) return; @@ -419,7 +418,7 @@ private void OnDownloadCommand() if (!item.Selected) continue; - if (!ignoreVersionMismatch && !selfVersion.NormalizedEquals(item.Info.ImagingToolsVersion)) + if (!ignoreVersionMismatch && !selfVersion.NormalizedEquals(item.Info.Version)) { if (Confirm(Res.ConfirmMessageResourceVersionMismatch, false)) ignoreVersionMismatch = true; diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs index 23a08cb..f5dc6d2 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/DownloadableResourceItem.cs @@ -79,7 +79,7 @@ private ValidationResultsCollection CreateValidationResults() { var result = new ValidationResultsCollection(); - if (!InstallationManager.ImagingToolsVersion.NormalizedEquals(Info.ImagingToolsVersion)) + if (!InstallationManager.ImagingToolsVersion.NormalizedEquals(Info.Version)) result.AddInfo(nameof(ImagingToolsVersion), Res.InfoMessageResourceVersionMismatch); if (!ResHelper.TryGetCulture(CultureName, out var _)) result.AddWarning(nameof(Language), Res.WarningMessageUnsupportedCulture); From 605929d5c42c140b6dffadcd1059cb0697a603b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 30 Jun 2021 20:29:56 +0200 Subject: [PATCH 192/211] Using an alternative address if HTTPS is not supported --- KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs index 98ad5a3..b92620b 100644 --- a/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs +++ b/KGySoft.Drawing.ImagingTools/_Classes/Configuration.cs @@ -68,7 +68,7 @@ private sealed class CultureInfoConverterFixed : CultureInfoConverter #region Constants private const string defaultResourceRepositoryLocation = "https://koszeggy.github.io/KGySoft.Drawing.Tools/res/"; // same as "https://raw.githubusercontent.com/koszeggy/KGySoft.Drawing.Tools/pages/res/" - private const string fallbackResourceRepositoryLocation = "http://koszeggy.github.io/KGySoft.Drawing.Tools/res/"; + private const string fallbackResourceRepositoryLocation = "http://kgysoft.net/res/"; // "http://koszeggy.github.io/KGySoft.Drawing.Tools/res/" does not work on Win7/.NET 3.5 #endregion @@ -105,6 +105,9 @@ private sealed class CultureInfoConverterFixed : CultureInfoConverter static Configuration() { allowHttps = !(OSUtils.IsMono && OSUtils.IsWindows); +#if NET35 + allowHttps &= OSUtils.IsWindows8OrLater; +#endif // To be able to resolve UserSettingsGroup of with other framework version AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; @@ -114,9 +117,6 @@ static Configuration() #endif #if NETFRAMEWORK - if (!allowHttps) - return; - try { // To be able to use HTTP requests with TLS 1.2 security protocol (may not work on Windows XP) From 35dddbfeb9bda1bb7e6f898cc64ce025dddeeeae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Wed, 30 Jun 2021 21:14:54 +0200 Subject: [PATCH 193/211] Fixing ScalingCheckBox size calculation in .NET 3.5 when visual styles are not applied --- KGySoft.Drawing.ImagingTools/View/Controls/ScalingCheckBox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingCheckBox.cs b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingCheckBox.cs index c6c745a..6030985 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/ScalingCheckBox.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/ScalingCheckBox.cs @@ -44,8 +44,8 @@ public override Size GetPreferredSize(Size proposedSize) Size result = base.GetPreferredSize(proposedSize); #if NET35 // The scaling is different in .NET 3.5 so instead if subtracting a constant padding difference - // we need to add some based on scaling, but only in high contrast mode - if (SystemInformation.HighContrast) + // we need to add some based on scaling, but only when visual styles are not applied + if (!Application.RenderWithVisualStyles) result.Width += this.ScaleWidth(2); #else result.Width -= 3; From 0485ec92f80d3e7cacf2fafdd17e317780040196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 1 Jul 2021 12:42:16 +0200 Subject: [PATCH 194/211] Adding an easter egg --- ...KGySoft.Drawing.ImagingTools.Messages.resx | 10 ++++++++ KGySoft.Drawing.ImagingTools/Res.cs | 7 ++++++ .../Forms/ImageVisualizerForm.Designer.cs | 23 +++++++++++++------ .../View/Forms/ImageVisualizerForm.cs | 5 ++++ KGySoft.Drawing.ImagingTools/View/Images.cs | 2 ++ .../ViewModel/ImageVisualizerViewModel.cs | 1 + 6 files changed, 41 insertions(+), 7 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx index 78e413d..0993f67 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.Messages.resx @@ -557,6 +557,13 @@ Target Platform: {1} You are now using the compiled English resources. Copyright © {2} KGy SOFT. All rights reserved. + + + Just a regular ToolStrip menu, eh? + +Now imagine every combination of target platforms (from .NET Framework 3.5 to .NET 5), operating systems (from Windows XP to Linux/Mono), different DPI settings, enabled/disabled visual styles and high contrast mode, right-to-left layout... + +Harmonizing visual elements for all possible environments is never a trivial task, but OMG, the ToolStrip wasn't a cakewalk. Would you believe that each and every combination had at least one rendering issue? My custom-zoomable ImageViewer control with the asynchronously generated resized interpolated images on multiple cores was an easy-peasy compared to that... N/A (KGySoft.Drawing.DebuggerVisualizers.dll is missing) @@ -926,6 +933,9 @@ Tip: use '[T]' to filter untranslated texts. About... + + Oh boy, this was tough... + &OK diff --git a/KGySoft.Drawing.ImagingTools/Res.cs b/KGySoft.Drawing.ImagingTools/Res.cs index 56430b0..0d335b5 100644 --- a/KGySoft.Drawing.ImagingTools/Res.cs +++ b/KGySoft.Drawing.ImagingTools/Res.cs @@ -309,6 +309,13 @@ internal static CultureInfo DisplayLanguage /// This item is for a different ImagingTools version. internal static string InfoMessageResourceVersionMismatch => Get("InfoMessage_ResourceVersionMismatch"); + /// Just a regular ToolStrip menu, eh? + /// + /// Now imagine every combination of target platforms (from .NET Framework 3.5 to .NET 5), operating systems (from Windows XP to Linux/Mono), different DPI settings, enabled/disabled visual styles and high contrast mode, right-to-left layout... + /// + /// Harmonizing visual elements for all possible environments is never a trivial task, but OMG, the ToolStrip wasn't a cakewalk. Would you believe that each and every combination had at least one rendering issue? My custom-zoomable ImageViewer control with the asynchronously generated resized interpolated images on multiple cores was an easy-peasy compared to that... + internal static string InfoMessageEasterEgg => Get("InfoMessage_EasterEgg"); + #endregion #region Installations diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs index f0de31b..32364ed 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.Designer.cs @@ -63,6 +63,7 @@ private void InitializeComponent() this.miSubmitResources = new System.Windows.Forms.ToolStripMenuItem(); this.miSeparatorAbout = new System.Windows.Forms.ToolStripSeparator(); this.miAbout = new System.Windows.Forms.ToolStripMenuItem(); + this.miEasterEgg = new System.Windows.Forms.ToolStripMenuItem(); this.btnConfiguration = new KGySoft.Drawing.ImagingTools.View.Components.AdvancedToolStripSplitButton(); this.miManageInstallations = new System.Windows.Forms.ToolStripMenuItem(); this.miLanguageSettings = new System.Windows.Forms.ToolStripMenuItem(); @@ -363,7 +364,8 @@ private void InitializeComponent() this.miMarketplace, this.miSubmitResources, this.miSeparatorAbout, - this.miAbout}); + this.miAbout, + this.miEasterEgg}); this.btnAbout.ImageTransparentColor = System.Drawing.Color.Magenta; this.btnAbout.Name = "btnAbout"; this.btnAbout.Size = new System.Drawing.Size(16, 22); @@ -372,39 +374,45 @@ private void InitializeComponent() // miWebSite // this.miWebSite.Name = "miWebSite"; - this.miWebSite.Size = new System.Drawing.Size(179, 22); + this.miWebSite.Size = new System.Drawing.Size(180, 22); this.miWebSite.Text = "miWebSite"; // // miGitHub // this.miGitHub.Name = "miGitHub"; - this.miGitHub.Size = new System.Drawing.Size(179, 22); + this.miGitHub.Size = new System.Drawing.Size(180, 22); this.miGitHub.Text = "miGitHub"; // // miMarketplace // this.miMarketplace.Name = "miMarketplace"; - this.miMarketplace.Size = new System.Drawing.Size(179, 22); + this.miMarketplace.Size = new System.Drawing.Size(180, 22); this.miMarketplace.Text = "miMarketplace"; // // miSubmitResources // this.miSubmitResources.Name = "miSubmitResources"; - this.miSubmitResources.Size = new System.Drawing.Size(179, 22); + this.miSubmitResources.Size = new System.Drawing.Size(180, 22); this.miSubmitResources.Text = "miSubmitResources"; // // miSeparatorAbout // this.miSeparatorAbout.Name = "miSeparatorAbout"; - this.miSeparatorAbout.Size = new System.Drawing.Size(176, 6); + this.miSeparatorAbout.Size = new System.Drawing.Size(177, 6); // // miAbout // this.miAbout.Name = "miAbout"; this.miAbout.ShortcutKeys = System.Windows.Forms.Keys.F1; - this.miAbout.Size = new System.Drawing.Size(179, 22); + this.miAbout.Size = new System.Drawing.Size(180, 22); this.miAbout.Text = "miAbout"; // + // miEasterEgg + // + this.miEasterEgg.Name = "miEasterEgg"; + this.miEasterEgg.Size = new System.Drawing.Size(180, 22); + this.miEasterEgg.Visible = false; + // // btnConfiguration // this.btnConfiguration.AutoChangeDefaultItem = true; @@ -523,5 +531,6 @@ private void InitializeComponent() private ToolStripMenuItem miSubmitResources; private ToolStripSeparator miSeparatorAbout; private ToolStripMenuItem miAbout; + private ToolStripMenuItem miEasterEgg; } } \ No newline at end of file diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs index f11d784..0b1c876 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/ImageVisualizerForm.cs @@ -124,6 +124,7 @@ protected override void ApplyResources() miLanguageSettings.Image = Images.Language; btnConfiguration.SetDefaultItem(miManageInstallations); + miEasterEgg.Image = Images.ImagingTools; btnAbout.Image = miAbout.Image = Icons.SystemInformation.ToScaledBitmap(); } @@ -342,12 +343,16 @@ private void InitCommandBindings() .AddSource(miMarketplace, nameof(miMarketplace.Click)); CommandBindings.Add(ViewModel.SubmitResourcesCommand) .AddSource(miSubmitResources, nameof(miSubmitResources.Click)); + CommandBindings.Add(ViewModel.ShowEasterEggCommand) + .AddSource(miEasterEgg, nameof(miEasterEgg.Click)); // View commands CommandBindings.Add(OnResizeCommand) .AddSource(this, nameof(Resize)); CommandBindings.Add(OnPreviewImageResizedCommand) .AddSource(imageViewer, nameof(imageViewer.SizeChanged)); + CommandBindings.Add(() => miEasterEgg.Visible |= ModifierKeys == (Keys.Shift | Keys.Control)) + .AddSource(miAbout, nameof(miAbout.MouseDown)); } private Rectangle GetScreenRectangle() => Screen.FromHandle(Handle).WorkingArea; diff --git a/KGySoft.Drawing.ImagingTools/View/Images.cs b/KGySoft.Drawing.ImagingTools/View/Images.cs index a40756a..f5cdea0 100644 --- a/KGySoft.Drawing.ImagingTools/View/Images.cs +++ b/KGySoft.Drawing.ImagingTools/View/Images.cs @@ -31,6 +31,7 @@ internal static class Images private static readonly Size referenceSize = new Size(16, 16); + private static Bitmap? imagingTools; private static Bitmap? check; private static Bitmap? crop; private static Bitmap? highlightVisibleClip; @@ -62,6 +63,7 @@ internal static class Images #region Properties + internal static Bitmap ImagingTools => imagingTools ??= GetResource(nameof(ImagingTools)); internal static Bitmap Check => check ??= GetResource(nameof(Check)); internal static Bitmap Crop => crop ??= GetResource(nameof(Crop)); internal static Bitmap HighlightVisibleClip => highlightVisibleClip ??= GetResource(nameof(HighlightVisibleClip)); diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs index 05f636f..8473267 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs @@ -160,6 +160,7 @@ internal ImageInfo ImageInfo internal ICommand VisitGitHubCommand => Get(() => new SimpleCommand(() => Process.Start("https://github.com/koszeggy/KGySoft.Drawing.Tools"))); internal ICommand VisitMarketplaceCommand => Get(() => new SimpleCommand(() => Process.Start("https://marketplace.visualstudio.com/items?itemName=KGySoft.drawing-debugger-visualizers"))); internal ICommand SubmitResourcesCommand => Get(() => new SimpleCommand(() => Process.Start("https://github.com/koszeggy/KGySoft.Drawing.Tools/issues/new?assignees=&labels=&template=submit-resources.md&title=%5BRes%5D"))); + internal ICommand ShowEasterEggCommand => Get(() => new SimpleCommand(() => ShowInfo(Res.InfoMessageEasterEgg))); #endregion From 3db067bd644af372197e1fb71ebd3e47ff4d8327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 1 Jul 2021 13:28:24 +0200 Subject: [PATCH 195/211] AdvancedToolStrip: Fixing selected menu item appearance when they have explicit colors --- .../View/Controls/AdvancedToolStrip.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index 30cc75a..b82c486 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -249,23 +249,23 @@ protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e.Graphics.DrawRectangle(SystemPens.Highlight, bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1); } -#if NET35 /// /// Changes to original: + /// - When a menu item is selected, then not using its possible custom colors /// - [HighContrast]: Fixing text color on highlighted menu items /// protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) { - if (SystemInformation.HighContrast && e.Item is ToolStripMenuItem mi) + if (e.Item is ToolStripMenuItem mi) { e.TextColor = !mi.Enabled ? SystemColors.GrayText - : mi.Selected || mi.Pressed ? SystemColors.HighlightText - : SystemColors.ControlText; + : SystemInformation.HighContrast ? mi.Selected || mi.Pressed ? SystemColors.HighlightText : SystemColors.ControlText + : mi.Selected || mi.Pressed ? SystemColors.ControlText + : e.Item.ForeColor; } base.OnRenderItemText(e); } -#endif /// /// Changes to original: From 7ea6e65e2542c6e8dd54e5573d3fc75595ffbc15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 1 Jul 2021 15:40:32 +0200 Subject: [PATCH 196/211] AdvancedToolStrip: reverting back to original checked border frame color on Windows XP --- .../View/Controls/AdvancedToolStrip.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs index b82c486..2bf9821 100644 --- a/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs +++ b/KGySoft.Drawing.ImagingTools/View/Controls/AdvancedToolStrip.cs @@ -136,30 +136,25 @@ static void RenderWithVisualStyles(Graphics g, ProfessionalColorTable colorTable { Color backgroundStart; Color backgroundEnd; - Color border; if ((style & ButtonStyle.Pressed) != 0 || (style & ButtonStyle.Selected) != 0 && (style & ButtonStyle.Checked) != 0) { backgroundStart = colorTable.ButtonPressedGradientBegin; backgroundEnd = colorTable.ButtonPressedGradientEnd; - border = colorTable.ButtonPressedBorder; } else if ((style & ButtonStyle.Selected) != 0) { backgroundStart = colorTable.ButtonSelectedGradientBegin; backgroundEnd = colorTable.ButtonSelectedGradientEnd; - border = colorTable.ButtonSelectedBorder; } else if ((style & ButtonStyle.Checked) != 0) { backgroundStart = colorTable.ButtonCheckedGradientBegin is { IsEmpty: false } c1 ? c1 : colorTable.ButtonCheckedHighlight; backgroundEnd = colorTable.ButtonCheckedGradientEnd is { IsEmpty: false } c2 ? c2 : colorTable.ButtonCheckedHighlight; - border = colorTable.ButtonCheckedHighlightBorder; } else return; FillBackground(g, bounds, backgroundStart, backgroundEnd); - g.DrawRectangle(border.GetPen(), bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1); } static void RenderBasicTheme(Graphics g, ProfessionalColorTable colorTable, Rectangle bounds, ButtonStyle style) @@ -169,7 +164,6 @@ static void RenderBasicTheme(Graphics g, ProfessionalColorTable colorTable, Rect : (style & ButtonStyle.Checked) != 0 ? colorTable.ButtonCheckedHighlight : Color.Empty; g.FillRectangle(backColor.GetBrush(), bounds); - g.DrawRectangle(colorTable.ButtonSelectedBorder.GetPen(), bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1); } #endregion @@ -187,6 +181,7 @@ static void RenderBasicTheme(Graphics g, ProfessionalColorTable colorTable, Rect RenderWithVisualStyles(g, colorTable, bounds, style); else RenderBasicTheme(g, colorTable, bounds, style); + g.DrawRectangle(colorTable.ButtonSelectedBorder.GetPen(), bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1); } private static void DrawHighContrastButtonBackground(Graphics g, Rectangle bounds, ButtonStyle style) From 81852938d68f408a2c420903de1a0cc0097f7e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 1 Jul 2021 15:41:02 +0200 Subject: [PATCH 197/211] About: fixing platform version information on .NET Core --- .../ViewModel/ImageVisualizerViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs index 8473267..ddc6d66 100644 --- a/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs +++ b/KGySoft.Drawing.ImagingTools/ViewModel/ImageVisualizerViewModel.cs @@ -1231,7 +1231,7 @@ private void OnShowAboutCommand() const string frameworkName = ".NET Framework 3.5"; #else TargetFrameworkAttribute attr = (TargetFrameworkAttribute)Attribute.GetCustomAttribute(asm, typeof(TargetFrameworkAttribute))!; - string frameworkName = attr.FrameworkDisplayName ?? attr.FrameworkName; + string frameworkName = attr.FrameworkDisplayName is { Length: > 0 } name ? name : attr.FrameworkName; #endif ShowInfo(Res.InfoMessageAbout(asm.GetName().Version!, frameworkName, DateTime.Now.Year)); } From ac87bd975560a7cead3252c247881146f6556fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 1 Jul 2021 15:49:27 +0200 Subject: [PATCH 198/211] Updating package readme --- .../KGySoft.Drawing.DebuggerVisualizers.Package.csproj | 1 + KGySoft.Drawing.DebuggerVisualizers.Package/readme.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj b/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj index 4c1836b..9639991 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/KGySoft.Drawing.DebuggerVisualizers.Package.csproj @@ -77,6 +77,7 @@ Designer + Designer diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md b/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md index 2cdd15a..6f909f6 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md @@ -1,4 +1,4 @@ -## KGy SOFT Drawing Debugger Visualizers +## KGy SOFT Drawing Debugger Visualizers for Visual Studio 2008-2019 This package provides debugger visualizers for several `System.Drawing` types such as `Image`, `Bitmap`, `Metafile`, `Icon`, `BitmapData`, `Graphics`, `ColorPalette`, `Color`. It supports multi-page, multi-resolution and animated images as well as saving them in various formats. @@ -42,7 +42,7 @@ See the [change log](https://github.com/koszeggy/KGySoft.Drawing.Tools/blob/mast
**A:** No, these visualizers are for `System.Drawing` types. But the built-in Dependency Object visualizer is able to display image sources anyway. **Q:** Where do I find the edited/downloaded resource files? Even my previously edited/downloaded resources have been disappeared. -
**A:** The language resources belong to the executed ImagingTools instance where they can be found in a `Resources` subfolder. The saved resources might be at different possible locations: +
**A:** The __Visual Studio/Tools/KGy SOFT Drawing Debugger Visualizers__ and clicking the magnifier icon executes the Imaging Tools from different locations. If you edit the language resources at one place they will not be automatically applied at the other place. Therefore, the saved resources might be at different possible locations: * If you execute a manually deployed version the resources will be in a `Resources` subfolder in the folder you executed the ImagingTools from * During debugging the tool is executed from the debugger visualizers folder: `Documents\Visual Studio \Visualizers` * If you launch the tool from the Visual Studio Tools menu, then it is located under `ProgramData\Microsoft\VisualStudio\Packages\...` From 4bb9da556f13b69700a1997ef4ccdcd186b1788a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 1 Jul 2021 15:53:00 +0200 Subject: [PATCH 199/211] Updating package readme --- KGySoft.Drawing.DebuggerVisualizers.Package/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md b/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md index 6f909f6..155bcc2 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md @@ -20,7 +20,7 @@ Several modifications are allowed on non-read-only images such as rotating, resi ## Installing Debugger Visualizers -* For Visual Studio 2013 and above you can use this VSIX package (tested with versions up to Visual Studio 2019). It will install the .NET 4.5 version. +* For Visual Studio 2013 and above you can use this VSIX package (tested with Visual Studio versions up to 2019). It will install the .NET 4.5 version, which works also for .NET Core projects. * For older Visual Studio versions and/or frameworks follow the [installation steps](https://github.com/koszeggy/KGySoft.Drawing.Tools#installing-debugger-visualizers) at the project site. ## Release Notes @@ -30,7 +30,7 @@ See the [change log](https://github.com/koszeggy/KGySoft.Drawing.Tools/blob/mast ## FAQ **Q:** Can I use the debugger visualizers for other Visual Studio versions? -
**A:** The VSIX installer supports Visual Studio 2013 and newer versions (tested until 2019). However, you can install the debugger visualizers manually for any version starting with Visual Studio 2008. See the [installation steps](https://github.com/koszeggy/KGySoft.Drawing.Tools#installing-debugger-visualizers) at the project site. +
**A:** The VSIX installer supports Visual Studio 2013 and newer versions (tested with versions up to 2019). However, you can install the debugger visualizers manually for any version starting with Visual Studio 2008. See the [installation steps](https://github.com/koszeggy/KGySoft.Drawing.Tools#installing-debugger-visualizers) at the project site. **Q:** Is Visual Studio Code supported?
**A:** As it has a completely different API, it is not supported yet. From f7f655caae6600f71cda36f59802cc8434000204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 1 Jul 2021 16:42:21 +0200 Subject: [PATCH 200/211] Update README.md --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 79774a2..ec345ba 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ KGy SOFT Drawing Tools repository contains [Debugger Visualizers](#debugger-visu ## Table of Contents 1. [KGy SOFT Imaging Tools](#kgy-soft-imaging-tools) + - [Localization](#localization) 2. [Debugger Visualizers](#debugger-visualizers) - [Installing Debugger Visualizers](#installing-debugger-visualizers) - [Troubleshooting](#troubleshooting) @@ -25,6 +26,19 @@ The Imaging Tools application makes possible to load images and icons from file, ![Changing Pixel Format with Quantizing and Dithering](https://kgysoft.net/images/Quantizing.png) +### Localization + +KGy SOFT Imaging Tools supports localization from .resx files. New language resources can be generated for any languages, and you can edit the texts within the application. The changes can be applied on-the-fly, without exiting the application. If you switch to a right-to-left language, then the layout is also immediately applied (at least on Windows). + +> _Tip:_ As a developer, you can use [KGy SOFT Core Libraries](https://github.com/koszeggy/KGySoft.CoreLibraries#dynamic-resource-management) if you want something similar in your application. + +The edited resources are saved in .resx files in the `Resources` subfolder of the application. Resources can be downloaded from within the application. + +![Editing resources in KGy SOFT Imaging Tools](https://user-images.githubusercontent.com/27336165/124143008-0a1e4f80-da8b-11eb-8f85-572507b66154.png) + +> _Note:_ If you create a localization for your language feel free to [submit a new issue](https://github.com/koszeggy/KGySoft.Drawing.Tools/issues/new?assignees=&labels=&template=submit-resources.md&title=%5BRes%5D) and I will make it available for everyone. Don't forget to mention your name in the translated About menu. + + ## Debugger Visualizers Imaging Tools is packed with several debugger visualizers for Visual Studio (compatible with all versions starting with Visual Studio 2008, and supports even .NET Core 2.1 and newer platform targets). When a type is debugged in Visual Studio and there is a debugger visualizer installed for that type, then a magnifier icon appears that you can click to open the visualizer. From 93ddb116aa19bbfe81bc60a1023f07467bff3406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 1 Jul 2021 16:51:31 +0200 Subject: [PATCH 201/211] Updating readme --- KGySoft.Drawing.DebuggerVisualizers.Package/readme.md | 4 ++-- README.md | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md b/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md index 155bcc2..bbc53e8 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md +++ b/KGySoft.Drawing.DebuggerVisualizers.Package/readme.md @@ -42,8 +42,8 @@ See the [change log](https://github.com/koszeggy/KGySoft.Drawing.Tools/blob/mast
**A:** No, these visualizers are for `System.Drawing` types. But the built-in Dependency Object visualizer is able to display image sources anyway. **Q:** Where do I find the edited/downloaded resource files? Even my previously edited/downloaded resources have been disappeared. -
**A:** The __Visual Studio/Tools/KGy SOFT Drawing Debugger Visualizers__ and clicking the magnifier icon executes the Imaging Tools from different locations. If you edit the language resources at one place they will not be automatically applied at the other place. Therefore, the saved resources might be at different possible locations: -* If you execute a manually deployed version the resources will be in a `Resources` subfolder in the folder you executed the ImagingTools from +
**A:** The _Visual Studio/Tools/KGy SOFT Drawing Debugger Visualizers_ and clicking the magnifier icon executes the Imaging Tools from different locations. If you edit the language resources at one place they will not be automatically applied at the other place. Therefore, the saved resources might be at different possible locations: +* If you execute a manually deployed version the resources will be in a `Resources` subfolder in the folder you executed Imaging Tools from * During debugging the tool is executed from the debugger visualizers folder: `Documents\Visual Studio \Visualizers` * If you launch the tool from the Visual Studio Tools menu, then it is located under `ProgramData\Microsoft\VisualStudio\Packages\...` diff --git a/README.md b/README.md index ec345ba..711a7ee 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,15 @@ KGy SOFT Imaging Tools supports localization from .resx files. New language reso > _Tip:_ As a developer, you can use [KGy SOFT Core Libraries](https://github.com/koszeggy/KGySoft.CoreLibraries#dynamic-resource-management) if you want something similar in your application. -The edited resources are saved in .resx files in the `Resources` subfolder of the application. Resources can be downloaded from within the application. +The edited resources are saved in .resx files in the `Resources` subfolder of the application. Resources can be downloaded from within the application as well. ![Editing resources in KGy SOFT Imaging Tools](https://user-images.githubusercontent.com/27336165/124143008-0a1e4f80-da8b-11eb-8f85-572507b66154.png) > _Note:_ If you create a localization for your language feel free to [submit a new issue](https://github.com/koszeggy/KGySoft.Drawing.Tools/issues/new?assignees=&labels=&template=submit-resources.md&title=%5BRes%5D) and I will make it available for everyone. Don't forget to mention your name in the translated About menu. +#### Help, my reasources are gone! + + ## Debugger Visualizers @@ -116,6 +119,11 @@ If Visual Studio cannot load the visualizer or you have other debugger visualize | Unable to load the custom visualizer (The UI-side visualizer type must derive from 'DialogDebuggerVisualizer').
![Unable to load the custom visualizer (The UI-side visualizer type must derive from 'DialogDebuggerVisualizer').](https://kgysoft.net/images/DebuggerVisualizerTrShUnableToLoadMustDeriveFrom.png) | The `Microsoft.VisualStudio.DebuggerVisualizers.dll` has been copied to the debugger visualizers installation folder. Recent Visual Studio versions can handle if a debugger visualizer references an unmatching version of this assembly but only if this assembly is not deployed along with the visualizers. | | Could not load file or assembly 'KGySoft.Drawing.DebuggerVisualizers.dll'.
![Could not load file or assembly 'KGySoft.Drawing.DebuggerVisualizers.dll'.](https://kgysoft.net/images/DebuggerVisualizerTrShCouldNotLoadVisualizer.png) or one of its dependencies. | Visual Studio 2008 supports the .NET 3.5 version only. A similar error may occur even if some files are missing. Just [install](#installing-debugger-visualizers) a correct version again. | | Value does not fall within the expected range.
![Value does not fall within the expected range.](https://kgysoft.net/images/DebuggerVisualizerTrShValueUnexpectedRange.png) | Windows XP does not support the .NET 4.5 version. | +| I edited the language resource files but I cannot find them (or they appear to be gone) | The _Visual Studio/Tools/KGy SOFT Drawing Debugger Visualizers_ and clicking the magnifier icon executes the Imaging Tools from different locations. If you edit the language resources at one place they will not be automatically applied at the other place. Therefore, the saved resources might be at different possible locations: +* If you execute a manually deployed version the resources will be in a `Resources` subfolder in the folder you executed the Imaging Tools from +* During debugging the tool is executed from the debugger visualizers folder: `Documents\Visual Studio \Visualizers` +* If you launch the tool from the Visual Studio Tools menu, then it is located under `ProgramData\Microsoft\VisualStudio\Packages\...` | + ## Download You can download the sources and the binaries as .zip archives [here](https://github.com/koszeggy/KGySoft.Drawing.Tools/releases). From 8f5d0363c626433dab46659cd5ab5c699d3894c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 1 Jul 2021 16:58:43 +0200 Subject: [PATCH 202/211] Updating the readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 711a7ee..8488cf7 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ KGy SOFT Drawing Tools repository contains [Debugger Visualizers](#debugger-visu - [Localization](#localization) 2. [Debugger Visualizers](#debugger-visualizers) - [Installing Debugger Visualizers](#installing-debugger-visualizers) - - [Troubleshooting](#troubleshooting) + - [Troubleshooting](#troubleshooting) 3. [Download](#download) 4. [Release Notes](#release-notes) 5. [Debugger Visualizers Test Tool](#debugger-visualizers-test-tool) @@ -40,7 +40,7 @@ The edited resources are saved in .resx files in the `Resources` subfolder of th #### Help, my reasources are gone! - +If you use Imaging Tools as debugger visualizers, then it can be executed from various locations. See the bottom of the [Troubleshooting](#troubleshooting) section below. ## Debugger Visualizers From d4413f6aff87d8ac3ac477b6ab9db71817ca38f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 1 Jul 2021 17:20:23 +0200 Subject: [PATCH 203/211] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8488cf7..e1de8fa 100644 --- a/README.md +++ b/README.md @@ -119,12 +119,13 @@ If Visual Studio cannot load the visualizer or you have other debugger visualize | Unable to load the custom visualizer (The UI-side visualizer type must derive from 'DialogDebuggerVisualizer').
![Unable to load the custom visualizer (The UI-side visualizer type must derive from 'DialogDebuggerVisualizer').](https://kgysoft.net/images/DebuggerVisualizerTrShUnableToLoadMustDeriveFrom.png) | The `Microsoft.VisualStudio.DebuggerVisualizers.dll` has been copied to the debugger visualizers installation folder. Recent Visual Studio versions can handle if a debugger visualizer references an unmatching version of this assembly but only if this assembly is not deployed along with the visualizers. | | Could not load file or assembly 'KGySoft.Drawing.DebuggerVisualizers.dll'.
![Could not load file or assembly 'KGySoft.Drawing.DebuggerVisualizers.dll'.](https://kgysoft.net/images/DebuggerVisualizerTrShCouldNotLoadVisualizer.png) or one of its dependencies. | Visual Studio 2008 supports the .NET 3.5 version only. A similar error may occur even if some files are missing. Just [install](#installing-debugger-visualizers) a correct version again. | | Value does not fall within the expected range.
![Value does not fall within the expected range.](https://kgysoft.net/images/DebuggerVisualizerTrShValueUnexpectedRange.png) | Windows XP does not support the .NET 4.5 version. | +| The forms are scaled incorrectly
![Incorrectly scaled image](https://user-images.githubusercontent.com/27336165/124148578-0e993700-da90-11eb-9c67-4e06e522795b.png) + | May happen if you use Imaging Tools from debugger visualizers, and you have just changed the DPI settings without signing out/in. However, signing in and out is not required if you execute the app directly. | | I edited the language resource files but I cannot find them (or they appear to be gone) | The _Visual Studio/Tools/KGy SOFT Drawing Debugger Visualizers_ and clicking the magnifier icon executes the Imaging Tools from different locations. If you edit the language resources at one place they will not be automatically applied at the other place. Therefore, the saved resources might be at different possible locations: * If you execute a manually deployed version the resources will be in a `Resources` subfolder in the folder you executed the Imaging Tools from * During debugging the tool is executed from the debugger visualizers folder: `Documents\Visual Studio \Visualizers` * If you launch the tool from the Visual Studio Tools menu, then it is located under `ProgramData\Microsoft\VisualStudio\Packages\...` | - ## Download You can download the sources and the binaries as .zip archives [here](https://github.com/koszeggy/KGySoft.Drawing.Tools/releases). From aab9ccd3ae058bfa457ff62826da66c7cfef6063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 1 Jul 2021 17:29:37 +0200 Subject: [PATCH 204/211] Update README.md --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e1de8fa..57492fa 100644 --- a/README.md +++ b/README.md @@ -119,12 +119,9 @@ If Visual Studio cannot load the visualizer or you have other debugger visualize | Unable to load the custom visualizer (The UI-side visualizer type must derive from 'DialogDebuggerVisualizer').
![Unable to load the custom visualizer (The UI-side visualizer type must derive from 'DialogDebuggerVisualizer').](https://kgysoft.net/images/DebuggerVisualizerTrShUnableToLoadMustDeriveFrom.png) | The `Microsoft.VisualStudio.DebuggerVisualizers.dll` has been copied to the debugger visualizers installation folder. Recent Visual Studio versions can handle if a debugger visualizer references an unmatching version of this assembly but only if this assembly is not deployed along with the visualizers. | | Could not load file or assembly 'KGySoft.Drawing.DebuggerVisualizers.dll'.
![Could not load file or assembly 'KGySoft.Drawing.DebuggerVisualizers.dll'.](https://kgysoft.net/images/DebuggerVisualizerTrShCouldNotLoadVisualizer.png) or one of its dependencies. | Visual Studio 2008 supports the .NET 3.5 version only. A similar error may occur even if some files are missing. Just [install](#installing-debugger-visualizers) a correct version again. | | Value does not fall within the expected range.
![Value does not fall within the expected range.](https://kgysoft.net/images/DebuggerVisualizerTrShValueUnexpectedRange.png) | Windows XP does not support the .NET 4.5 version. | -| The forms are scaled incorrectly
![Incorrectly scaled image](https://user-images.githubusercontent.com/27336165/124148578-0e993700-da90-11eb-9c67-4e06e522795b.png) - | May happen if you use Imaging Tools from debugger visualizers, and you have just changed the DPI settings without signing out/in. However, signing in and out is not required if you execute the app directly. | -| I edited the language resource files but I cannot find them (or they appear to be gone) | The _Visual Studio/Tools/KGy SOFT Drawing Debugger Visualizers_ and clicking the magnifier icon executes the Imaging Tools from different locations. If you edit the language resources at one place they will not be automatically applied at the other place. Therefore, the saved resources might be at different possible locations: -* If you execute a manually deployed version the resources will be in a `Resources` subfolder in the folder you executed the Imaging Tools from -* During debugging the tool is executed from the debugger visualizers folder: `Documents\Visual Studio \Visualizers` -* If you launch the tool from the Visual Studio Tools menu, then it is located under `ProgramData\Microsoft\VisualStudio\Packages\...` | +| The app looks blurry. | If you changed the DPI settins, you need to restart the application. Per-monitor DPI awareness is not supported. | +| The visual elements are scaled incorrectly.
![Incorrectly scaled image](https://user-images.githubusercontent.com/27336165/124148578-0e993700-da90-11eb-9c67-4e06e522795b.png) | May happen if you use Imaging Tools from debugger visualizers, and you have just changed the DPI settings without signing out/in. However, signing in and out is not required if you execute the app directly. | +| I edited the language resource files but I cannot find them (or they appear to be gone) | The _Visual Studio/Tools/KGy SOFT Drawing Debugger Visualizers_ and clicking the magnifier icon executes the Imaging Tools from different locations. If you edit the language resources at one place they will not be automatically applied at the other place. Therefore, the saved resources might be at different possible locations:
• If you execute a manually deployed version the resources will be in a `Resources` subfolder in the folder you executed the Imaging Tools from.
• During debugging the tool is executed from the debugger visualizers folder: `Documents\Visual Studio \Visualizers`
• If you launch the tool from the Visual Studio Tools menu, then it is located under `ProgramData\Microsoft\VisualStudio\Packages\...` | ## Download You can download the sources and the binaries as .zip archives [here](https://github.com/koszeggy/KGySoft.Drawing.Tools/releases). From df90b28af956b2642b9df382c20849412006992b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 1 Jul 2021 17:40:00 +0200 Subject: [PATCH 205/211] Fixing InvalidOperationException in .NET 5 if closing the form while it is in editing state. --- .../View/Forms/EditResourcesForm.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs index d1dc329..f135388 100644 --- a/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs +++ b/KGySoft.Drawing.ImagingTools/View/Forms/EditResourcesForm.cs @@ -81,6 +81,17 @@ protected override void ApplyViewModel() base.ApplyViewModel(); } + protected override void OnFormClosing(FormClosingEventArgs e) + { + if (gridResources.IsCurrentCellInEditMode) + { + gridResources.CancelEdit(); + gridResources.EndEdit(); + } + + base.OnFormClosing(e); + } + #endregion #region Private Methods From dfcdd41511316323ef4005e9119b5c0a3e1ab1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Thu, 1 Jul 2021 17:55:55 +0200 Subject: [PATCH 206/211] Updating readme --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 57492fa..57e7520 100644 --- a/README.md +++ b/README.md @@ -76,10 +76,11 @@ You can perform the install also from Visual Studio by the _Tools/Extensions and 1. [Download](#download) the binaries and extract the .7z archive to any folder. 2. Open the folder with the extracted content. You will find three folders there: - - `net35` contains the .NET 3.5 version. Compatible with all Visual Studio versions starting with Visual Studio 2008 (tested with versions 2008-2019). Cannot be used to debug .NET Core applications. - - `net40` contains the .NET 4.0 version. You cannot use this one for Visual Studio 2008. Cannot be used to debug .NET Core applications. - - `net45` contains the .NET 4.5 version. You cannot use this one for Windows XP and Visual Studio 2008/2010. With some limitations supports also .NET Core projects (in case of issues see the [Troubleshooting](#Troubleshooting) section). + - `net35` contains the .NET 3.5 version. Compatible with all Visual Studio versions starting with Visual Studio 2008 (tested with versions 2008-2019). Cannot be used to debug .NET Core applications. As a standalone application, this version supports all Windows versions starting with Windows XP and it can be executed also on Linux (with Mono). + - `net40` contains the .NET 4.0 version. You cannot use this one for Visual Studio 2008. Cannot be used to debug .NET Core applications. As a standalone application, this version supports all Windows versions starting with Windows XP, and it can be executed also on Linux (with Mono). + - `net45` contains the .NET 4.5 version. You cannot use this one for Windows XP and Visual Studio 2008/2010. With some limitations supports also .NET Core projects (in case of issues see the [Troubleshooting](#Troubleshooting) section). Using Mono, it can be executed also on Linux as a standalone application. - `netcoreapp3.0` contains the .NET Core 3.0 binaries of the Imaging Tools application. Debugger visualizers are not included because it would not be recognized by Visual Studio anyway. + - `net5.0-windows` contains the .NET 5.0 binaries of the Imaging Tools application. Debugger visualizers are not included because it would not be recognized by Visual Studio anyway. As a standalone application, this is the recommended version on Windows 7-11. 3. Execute `KGySoft.Drawing.ImagingTools.exe` from one of the folders listed above. Click the _Manage Debugger Visualizer Installations..._ button (the gear icon) on the toolbar. ![Select Install](https://kgysoft.net/images/InstallSelectMenu.png) @@ -124,7 +125,7 @@ If Visual Studio cannot load the visualizer or you have other debugger visualize | I edited the language resource files but I cannot find them (or they appear to be gone) | The _Visual Studio/Tools/KGy SOFT Drawing Debugger Visualizers_ and clicking the magnifier icon executes the Imaging Tools from different locations. If you edit the language resources at one place they will not be automatically applied at the other place. Therefore, the saved resources might be at different possible locations:
• If you execute a manually deployed version the resources will be in a `Resources` subfolder in the folder you executed the Imaging Tools from.
• During debugging the tool is executed from the debugger visualizers folder: `Documents\Visual Studio \Visualizers`
• If you launch the tool from the Visual Studio Tools menu, then it is located under `ProgramData\Microsoft\VisualStudio\Packages\...` | ## Download -You can download the sources and the binaries as .zip archives [here](https://github.com/koszeggy/KGySoft.Drawing.Tools/releases). +You can download the sources and the binaries as .7z archives [here](https://github.com/koszeggy/KGySoft.Drawing.Tools/releases). ## Debugger Visualizers Test Tool From 202471911394a75560cf89d33e3ddae8a7a65cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 2 Jul 2021 13:34:46 +0200 Subject: [PATCH 207/211] Updating and centering images --- README.md | 44 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 57e7520..76bc846 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,14 @@ # KGy SOFT Drawing Tools -KGy SOFT Drawing Tools repository contains [Debugger Visualizers](#debugger-visualizers) for several `System.Drawing` types such as `Bitmap`, `Metafile`, `Icon`, `BitmapData`, `Graphics`, etc. (see also below). The visualizers use [KGy SOFT Imaging Tools](#kgy-soft-imaging-tools) to display these types visually, which can be executed as a standalone application as well. Along with the [Debugger Visualizers Test Tool](#debugger-visualizers-test-tool) it can be considered also as a demonstration of the features of the [KGy SOFT Drawing Libraries](https://kgysoft.net/drawing). +The KGy SOFT Drawing Tools repository contains a couple of applications and some [Debugger Visualizers](#debugger-visualizers) for several `System.Drawing` types such as `Bitmap`, `Metafile`, `Icon`, `BitmapData`, `Graphics`, etc. (see also below). The visualizers use [KGy SOFT Imaging Tools](#kgy-soft-imaging-tools) to display these types visually, which can be executed as a standalone application as well. Along with the [Debugger Visualizers Test Tool](#debugger-visualizers-test-tool) it can be considered also as a demonstration of the features of the [KGy SOFT Drawing Libraries](https://kgysoft.net/drawing). [![Website](https://img.shields.io/website/https/kgysoft.net/corelibraries.svg)](https://kgysoft.net/drawing) [![Drawing Libraries Repo](https://img.shields.io/github/repo-size/koszeggy/KGySoft.Drawing.svg?label=Drawing%20Libraries)](https://github.com/koszeggy/KGySoft.Drawing) ## Table of Contents 1. [KGy SOFT Imaging Tools](#kgy-soft-imaging-tools) + - [Compatibility](#compatibility) - [Localization](#localization) 2. [Debugger Visualizers](#debugger-visualizers) - [Installing Debugger Visualizers](#installing-debugger-visualizers) @@ -20,11 +21,28 @@ KGy SOFT Drawing Tools repository contains [Debugger Visualizers](#debugger-visu ## KGy SOFT Imaging Tools -![KGySoft.Drawing.ImagingTools](https://kgysoft.net/images/ImagingTools.png) +

+ KGySoft Imaging Tools on Windows 10 +
KGy SOFT Imaging Tools on Windows 10 +

The Imaging Tools application makes possible to load images and icons from file, manipulate and save them into various formats. Several modifications are possible such as rotating, resizing, changing pixel format with quantizing and dithering, adjusting brightness, contrast and gamma, or even editing the palette entries of indexed bitmaps. -![Changing Pixel Format with Quantizing and Dithering](https://kgysoft.net/images/Quantizing.png) +

+ Changing Pixel Format with Quantizing and Dithering +
Changing Pixel Format with Quantizing and Dithering +

+ +> _Tip:_ As a developer, you can access all of these image manipulaion functions by using [KGy SOFT Drawing Libraries](https://github.com/koszeggy/KGySoft.Drawing). It supports not just `System.Drawing` types but also completely managed and technology agnostic bitmap data manipulation as well. + +### Compatibility + +KGy SOFT Imaging Tools supports a wide range of platforms. Windows is supported starting with Windows XP but by using [Mono](https://www.mono-project.com/download/stable/) you can execute it also on Linux. See the [downloads](#download) for details. + +

+ KGySoft Imaging Tools on Ubuntu Linux, using dark theme +
KGySoft Imaging Tools on Ubuntu Linux, using dark theme +

### Localization @@ -34,7 +52,10 @@ KGy SOFT Imaging Tools supports localization from .resx files. New language reso The edited resources are saved in .resx files in the `Resources` subfolder of the application. Resources can be downloaded from within the application as well. -![Editing resources in KGy SOFT Imaging Tools](https://user-images.githubusercontent.com/27336165/124143008-0a1e4f80-da8b-11eb-8f85-572507b66154.png) +

+ Editing resources in KGy SOFT Imaging Tools +
Editing resources in KGy SOFT Imaging Tools +

> _Note:_ If you create a localization for your language feel free to [submit a new issue](https://github.com/koszeggy/KGySoft.Drawing.Tools/issues/new?assignees=&labels=&template=submit-resources.md&title=%5BRes%5D) and I will make it available for everyone. Don't forget to mention your name in the translated About menu. @@ -46,11 +67,17 @@ If you use Imaging Tools as debugger visualizers, then it can be executed from v Imaging Tools is packed with several debugger visualizers for Visual Studio (compatible with all versions starting with Visual Studio 2008, and supports even .NET Core 2.1 and newer platform targets). When a type is debugged in Visual Studio and there is a debugger visualizer installed for that type, then a magnifier icon appears that you can click to open the visualizer. -![Debugger Visualizer Usage](https://kgysoft.net/images/DebuggerVisualizerUsage.png) +

+ Debugger Visualizer Usage +
Debugger Visualizer Usage +

Either click the magnifier icon or choose a debugger visualizer from the drop down list (if more visualizers are applicable). -![Debugging Graphics](https://kgysoft.net/images/DebugGraphics.png) +

+ Debugging a Graphics instance +
Debugging a Graphics instance +

The `KGySoft.Drawing.DebuggerVisualizers` assembly provides debugger visualizers for the following types: - `System.Drawing.Image`: If executed for a non read-only variable or member of type `Image`, then the actual value can be replaced by any `Bitmap` or `Metafile`. @@ -62,7 +89,10 @@ The `KGySoft.Drawing.DebuggerVisualizers` assembly provides debugger visualizers - `System.Drawing.Imaging.ColorPalette`: In a non read-only context the colors can be edited. - `System.Drawing.Color`: In a non read-only context the color can be replaced. -![Debugging Palette](https://kgysoft.net/images/DebugPalette.png) +

+ Debugging a Palette instance +
Debugging a Palette instance +

### Installing Debugger Visualizers From 05d44de7919f711150e4544257964add1d85ff58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 2 Jul 2021 14:43:49 +0200 Subject: [PATCH 208/211] Updating and centering images --- README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 76bc846..d7cb19f 100644 --- a/README.md +++ b/README.md @@ -113,17 +113,26 @@ You can perform the install also from Visual Studio by the _Tools/Extensions and - `net5.0-windows` contains the .NET 5.0 binaries of the Imaging Tools application. Debugger visualizers are not included because it would not be recognized by Visual Studio anyway. As a standalone application, this is the recommended version on Windows 7-11. 3. Execute `KGySoft.Drawing.ImagingTools.exe` from one of the folders listed above. Click the _Manage Debugger Visualizer Installations..._ button (the gear icon) on the toolbar. -![Select Install](https://kgysoft.net/images/InstallSelectMenu.png) +

+ Installing Debugger Visualizers from Imaging Tools +
Installing Debugger Visualizers from Imaging Tools +

> _Note:_ Starting with version 2.1.0 the debugger visualizers can be used also for .NET Core projects from Visual Studio 2019, even though no .NET Core binaries are used. 4. In the drop down list you will see the identified Visual Studio versions in your Documents folder. You can select either one of them or the _<Custom Path...>_ menu item to install the visualizer debuggers into a custom folder. -![Select Visual Studio version](https://kgysoft.net/images/InstallSelectVSVersion.png) +

+ Selecting Visual Studio Version +
Selecting Visual Studio Version +

5. Click on the _Install_ button. On success the status will display the installed version. -![Installation Complete](https://kgysoft.net/images/InstallComplete.png) +

+ Installation Complete +
Installation Complete +

### Troubleshooting @@ -161,7 +170,10 @@ You can download the sources and the binaries as .7z archives [here](https://git A simple test application is also available in the download binaries. Though it was created mainly for testing purposes it also demonstrates the debugger visualizer and some `KGySoft.Drawing` features. -![Debugger Visualizer Test App](https://kgysoft.net/images/DebuggerVisualizerTest.png) +

+ Debugger Visualizer Test Tool +
Debugger Visualizer Test Tool +

> _Note:_ The Debugger Visualizers Test Tool directly references a specific version of the `Microsoft.VisualStudio.DebuggerVisualizers` assembly, therefore Visual Studio will not able to display visualizers when debugging this project unless you use the very same version (Visual Studio 2013). From 9b5c8fc7b7d2c38b5c37e62d9a9b1910eba7780e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 2 Jul 2021 16:28:31 +0200 Subject: [PATCH 209/211] Updating readme file --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d7cb19f..d4a6b6d 100644 --- a/README.md +++ b/README.md @@ -105,12 +105,11 @@ You can perform the install also from Visual Studio by the _Tools/Extensions and #### Manual Install 1. [Download](#download) the binaries and extract the .7z archive to any folder. -2. Open the folder with the extracted content. You will find three folders there: - - `net35` contains the .NET 3.5 version. Compatible with all Visual Studio versions starting with Visual Studio 2008 (tested with versions 2008-2019). Cannot be used to debug .NET Core applications. As a standalone application, this version supports all Windows versions starting with Windows XP and it can be executed also on Linux (with Mono). - - `net40` contains the .NET 4.0 version. You cannot use this one for Visual Studio 2008. Cannot be used to debug .NET Core applications. As a standalone application, this version supports all Windows versions starting with Windows XP, and it can be executed also on Linux (with Mono). - - `net45` contains the .NET 4.5 version. You cannot use this one for Windows XP and Visual Studio 2008/2010. With some limitations supports also .NET Core projects (in case of issues see the [Troubleshooting](#Troubleshooting) section). Using Mono, it can be executed also on Linux as a standalone application. - - `netcoreapp3.0` contains the .NET Core 3.0 binaries of the Imaging Tools application. Debugger visualizers are not included because it would not be recognized by Visual Studio anyway. - - `net5.0-windows` contains the .NET 5.0 binaries of the Imaging Tools application. Debugger visualizers are not included because it would not be recognized by Visual Studio anyway. As a standalone application, this is the recommended version on Windows 7-11. +2. Open the folder with the extracted content. You will find four folders there: + - `net35` contains the .NET Framework 3.5 version. Compatible with all Visual Studio versions starting with Visual Studio 2008 (tested with versions 2008-2019). Cannot be used to debug .NET Core applications. + - `net40` contains the .NET Framework 4.0 version. You cannot use this one for Visual Studio 2008. Cannot be used to debug .NET Core applications. + - `net45` contains the .NET Framework 4.5 version. You cannot use this one for Windows XP and Visual Studio 2008/2010. With some limitations supports also .NET Core/.NET projects (in case of issues see the [Troubleshooting](#Troubleshooting) section). + - `net5.0-windows` contains the .NET 5.0 binaries of the Imaging Tools application. Debugger visualizers are not included because it would not be recognized by Visual Studio anyway. 3. Execute `KGySoft.Drawing.ImagingTools.exe` from one of the folders listed above. Click the _Manage Debugger Visualizer Installations..._ button (the gear icon) on the toolbar.

@@ -164,11 +163,17 @@ If Visual Studio cannot load the visualizer or you have other debugger visualize | I edited the language resource files but I cannot find them (or they appear to be gone) | The _Visual Studio/Tools/KGy SOFT Drawing Debugger Visualizers_ and clicking the magnifier icon executes the Imaging Tools from different locations. If you edit the language resources at one place they will not be automatically applied at the other place. Therefore, the saved resources might be at different possible locations:
• If you execute a manually deployed version the resources will be in a `Resources` subfolder in the folder you executed the Imaging Tools from.
• During debugging the tool is executed from the debugger visualizers folder: `Documents\Visual Studio \Visualizers`
• If you launch the tool from the Visual Studio Tools menu, then it is located under `ProgramData\Microsoft\VisualStudio\Packages\...` | ## Download -You can download the sources and the binaries as .7z archives [here](https://github.com/koszeggy/KGySoft.Drawing.Tools/releases). +You can download the sources and the binaries as .7z/.zip archives at the [releases](https://github.com/koszeggy/KGySoft.Drawing.Tools/releases) page. + +To support the widest possible range of platforms the binaries archive contains multiple builds in different folders. +* `net35`: This contains the .NET Framework 3.5 build and though it works on every platforms Imaging Tools supports, it is not really recommended to use as a standalone application. If you use Imaging Tools as [debugger visualizers](#installing-debugger-visualizers), then it is the only version you can use for Visual Studio 2008. For newer Visual Studio versions use it only if you want to debug a .NET Framework 2.0-3.5 application. +* `net40`: This is the .NET Framework 4.0 build. As a standalone application, it's basically recommended only for Windows XP. +* `net45`: This is the .NET Framework 4.5 build, which is the recommended version to use as a debugger visualizer for .NET Framework 4.x and .NET Core projects (including .NET 5 and newer platforms). As a standalone application, this is also the recommended version for Linux (though 3.5/4.0 also support it). +* `net5.0-windows`: This folder contains the .NET 5.0 build. As a standalone application this is the recommended version for Windows 7 and above. On the other hand, this one cannot be used as a debugger visualizer (even for .NET Core projects) and does not support Linux either. ## Debugger Visualizers Test Tool -A simple test application is also available in the download binaries. Though it was created mainly for testing purposes it also demonstrates the debugger visualizer and some `KGySoft.Drawing` features. +A simple test application is also available in the download binaries. Though it was created mainly for testing purposes it also demonstrates how to use the public API of the Imaging Tools application and the DebuggerVisualizers from a consumer library or application.

Debugger Visualizer Test Tool From 7d10bcae915061507b7de6157d251260f0168f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 2 Jul 2021 16:53:14 +0200 Subject: [PATCH 210/211] Updating logo to fit also for dark background --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4a6b6d..e1f349a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![KGy SOFT .net](http://docs.kgysoft.net/drawing/icons/logo.png)](https://kgysoft.net) +[![KGy SOFT .net](https://user-images.githubusercontent.com/27336165/124292367-c93f3d00-db55-11eb-8003-6d943ee7d7fa.png)](https://kgysoft.net) # KGy SOFT Drawing Tools From 23b1aff2e40aa1dc14623c7efaa539f8aa7b83ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20K=C5=91szeg?= Date: Fri, 2 Jul 2021 16:55:11 +0200 Subject: [PATCH 211/211] Removing the .NET Core 3.0 build --- .../KGySoft.Drawing.DebuggerVisualizers.Test.csproj | 2 +- .../KGySoft.Drawing.DebuggerVisualizers.csproj | 2 +- .../KGySoft.Drawing.ImagingTools.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj b/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj index 776578f..5ef8f4c 100644 --- a/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers.Test/KGySoft.Drawing.DebuggerVisualizers.Test.csproj @@ -1,7 +1,7 @@  - net35;net40;net45;netcoreapp3.0;net5.0-windows + net35;net40;net45;net5.0-windows false KGySoft.Drawing.DebuggerVisualizers.Test diff --git a/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj b/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj index 48e7112..303d319 100644 --- a/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj +++ b/KGySoft.Drawing.DebuggerVisualizers/KGySoft.Drawing.DebuggerVisualizers.csproj @@ -1,7 +1,7 @@  - net35;net40;net45;netcoreapp3.0;net5.0-windows + net35;net40;net45;net5.0-windows false KGySoft.Drawing.DebuggerVisualizers diff --git a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj index c8a14ab..bf499e4 100644 --- a/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj +++ b/KGySoft.Drawing.ImagingTools/KGySoft.Drawing.ImagingTools.csproj @@ -1,7 +1,7 @@  - net35;net40;net45;netcoreapp3.0;net5.0-windows + net35;net40;net45;net5.0-windows false KGySoft.Drawing.ImagingTools