[fxcop] image resizer ui (#6841)

* adjustments

* Settings fixed

* Getting resizing tests operational again

* fixed default vs loading from settings

* one small tewak
This commit is contained in:
Clint Rutkas
2020-10-01 11:33:23 -07:00
committed by GitHub
parent 19b519638f
commit c219fe0d1d
21 changed files with 141 additions and 112 deletions

View File

@@ -62,7 +62,7 @@ namespace ImageResizer.Models
batch.Files.Add("Image1.jpg");
batch.Files.Add("Image2.jpg");
var errors = batch.Process(CancellationToken.None, (_, __) => { }).ToList();
var errors = batch.Process((_, __) => { }, CancellationToken.None).ToList();
Assert.Equal(2, errors.Count);
@@ -89,8 +89,8 @@ namespace ImageResizer.Models
var calls = new ConcurrentBag<(int i, double count)>();
batch.Process(
CancellationToken.None,
(i, count) => calls.Add((i, count)));
(i, count) => calls.Add((i, count)),
CancellationToken.None);
Assert.Equal(2, calls.Count);
Assert.Contains(calls, c => c.i == 1 && c.count == 2);

View File

@@ -430,19 +430,19 @@ namespace ImageResizer.Models
private static Settings Settings(Action<Settings> action = null)
{
var settings = new Settings
var settings = new Settings()
{
Sizes = new ObservableCollection<ResizeSize>
{
new ResizeSize
{
Name = "Test",
Width = 96,
Height = 96,
},
},
SelectedSizeIndex = 0,
};
settings.Sizes.Clear();
settings.Sizes.Add(new ResizeSize
{
Name = "Test",
Width = 96,
Height = 96,
});
action?.Invoke(settings);
return settings;

View File

@@ -30,9 +30,10 @@ namespace ImageResizer.Properties
{
var settings = new Settings
{
Sizes = new ObservableCollection<ResizeSize>(),
CustomSize = new CustomSize(),
};
settings.Sizes.Clear();
var ncc = (INotifyCollectionChanged)settings.AllSizes;
var result = AssertEx.Raises<NotifyCollectionChangedEventArgs>(
@@ -48,10 +49,10 @@ namespace ImageResizer.Properties
{
var settings = new Settings
{
Sizes = new ObservableCollection<ResizeSize>(),
CustomSize = new CustomSize(),
};
settings.Sizes.Clear();
Assert.PropertyChanged(
(INotifyPropertyChanged)settings.AllSizes,
"Item[]",
@@ -63,10 +64,10 @@ namespace ImageResizer.Properties
{
var settings = new Settings
{
Sizes = new ObservableCollection<ResizeSize> { new ResizeSize() },
CustomSize = new CustomSize(),
};
settings.Sizes.Add(new ResizeSize());
Assert.Contains(settings.Sizes[0], settings.AllSizes);
}
@@ -75,9 +76,9 @@ namespace ImageResizer.Properties
{
var settings = new Settings
{
Sizes = new ObservableCollection<ResizeSize>(),
CustomSize = new CustomSize(),
};
settings.Sizes.Clear();
Assert.Contains(settings.CustomSize, settings.AllSizes);
}
@@ -88,9 +89,10 @@ namespace ImageResizer.Properties
var originalCustomSize = new CustomSize();
var settings = new Settings
{
Sizes = new ObservableCollection<ResizeSize>(),
CustomSize = originalCustomSize,
};
settings.Sizes.Clear();
var ncc = (INotifyCollectionChanged)settings.AllSizes;
var result = AssertEx.Raises<NotifyCollectionChangedEventArgs>(
@@ -126,9 +128,9 @@ namespace ImageResizer.Properties
var settings = new Settings
{
SelectedSizeIndex = index,
Sizes = new ObservableCollection<ResizeSize>(),
CustomSize = new CustomSize(),
};
settings.Sizes.Clear();
var result = settings.SelectedSize;
@@ -141,12 +143,9 @@ namespace ImageResizer.Properties
var settings = new Settings
{
SelectedSizeIndex = 0,
Sizes = new ObservableCollection<ResizeSize>
{
new ResizeSize(),
},
};
settings.Sizes.Add(new ResizeSize());
var result = settings.SelectedSize;
Assert.Same(settings.Sizes[0], result);
@@ -265,7 +264,6 @@ namespace ImageResizer.Properties
Assert.PropertyChanged(settings, "PngInterlaceOption", action);
Assert.PropertyChanged(settings, "TiffCompressOption", action);
Assert.PropertyChanged(settings, "FileName", action);
Assert.PropertyChanged(settings, "Sizes", action);
Assert.PropertyChanged(settings, "KeepDateModified", action);
Assert.PropertyChanged(settings, "FallbackEncoder", action);
Assert.PropertyChanged(settings, "CustomSize", action);

View File

@@ -24,7 +24,7 @@ namespace ImageResizer
protected override void OnStartup(StartupEventArgs e)
{
var batch = ResizeBatch.FromCommandLine(Console.In, e.Args);
var batch = ResizeBatch.FromCommandLine(Console.In, e?.Args);
// TODO: Add command-line parameters that can be used in lieu of the input page (issue #14)
var mainWindow = new MainWindow(new MainViewModel(batch, Settings.Default));
@@ -34,12 +34,12 @@ namespace ImageResizer
BecomeForegroundWindow(new System.Windows.Interop.WindowInteropHelper(mainWindow).Handle);
}
private void BecomeForegroundWindow(IntPtr hWnd)
private static void BecomeForegroundWindow(IntPtr hWnd)
{
Win32Helpers.INPUT input = new Win32Helpers.INPUT { type = Win32Helpers.INPUTTYPE.INPUT_MOUSE, data = { } };
Win32Helpers.INPUT[] inputs = new Win32Helpers.INPUT[] { input };
Win32Helpers.SendInput(1, inputs, Win32Helpers.INPUT.Size);
Win32Helpers.SetForegroundWindow(hWnd);
NativeMethods.INPUT input = new NativeMethods.INPUT { type = NativeMethods.INPUTTYPE.INPUT_MOUSE, data = { } };
NativeMethods.INPUT[] inputs = new NativeMethods.INPUT[] { input };
_ = NativeMethods.SendInput(1, inputs, NativeMethods.INPUT.Size);
NativeMethods.SetForegroundWindow(hWnd);
}
}
}

View File

@@ -109,7 +109,7 @@
<Compile Include="Models\ResizeSize.cs" />
<Compile Include="Models\ResizeUnit.cs" />
<Compile Include="Utilities\MathHelpers.cs" />
<Compile Include="Utilities\Win32Helpers.cs" />
<Compile Include="Utilities\NativeMethods.cs" />
<Compile Include="ViewModels\AdvancedViewModel.cs" />
<Compile Include="ViewModels\InputViewModel.cs" />
<Compile Include="ViewModels\ITabViewModel.cs" />
@@ -213,6 +213,11 @@
<Resource Include="Resources\ImageResizer.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers">
<Version>3.3.0</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="MvvmLightLibs">
<Version>5.4.1.1</Version>
</PackageReference>

View File

@@ -1,11 +1,8 @@
// This class sets the visibility property of Advanced settings based on the OS Version
using System;
using System.Runtime.InteropServices;
namespace ImageResizer.Models
{
public class AdvancedSettings
public static class AdvancedSettings
{
public static bool UseNewSettings()
{

View File

@@ -24,12 +24,15 @@ namespace ImageResizer.Models
// NB: We read these from stdin since there are limits on the number of args you can have
string file;
while ((file = standardInput.ReadLine()) != null)
if (standardInput != null)
{
batch.Files.Add(file);
while ((file = standardInput.ReadLine()) != null)
{
batch.Files.Add(file);
}
}
for (var i = 0; i < args.Length; i++)
for (var i = 0; i < args?.Length; i++)
{
if (args[i] == "/d")
{
@@ -43,9 +46,7 @@ namespace ImageResizer.Models
return batch;
}
public IEnumerable<ResizeError> Process(
CancellationToken cancellationToken,
Action<int, double> reportProgress)
public IEnumerable<ResizeError> Process(Action<int, double> reportProgress, CancellationToken cancellationToken)
{
double total = Files.Count;
var completed = 0;
@@ -66,7 +67,9 @@ namespace ImageResizer.Models
{
Execute(file);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
errors.Add(new ResizeError { File = Path.GetFileName(file), Error = ex.Message });
}

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
@@ -175,6 +176,7 @@ namespace ImageResizer.Models
}
var fileName = string.Format(
CultureInfo.CurrentCulture,
_settings.FileNameFormat,
originalFileName,
_settings.SelectedSize.Name,

View File

@@ -13,7 +13,13 @@ namespace ImageResizer.Models
[JsonObject(MemberSerialization.OptIn)]
public class ResizeSize : ObservableObject
{
private static readonly IDictionary<string, string> _tokens;
private static readonly IDictionary<string, string> _tokens = new Dictionary<string, string>
{
["$small$"] = Resources.Small,
["$medium$"] = Resources.Medium,
["$large$"] = Resources.Large,
["$phone$"] = Resources.Phone,
};
private string _name;
private ResizeFit _fit = ResizeFit.Fit;
@@ -22,15 +28,6 @@ namespace ImageResizer.Models
private bool _showHeight = true;
private ResizeUnit _unit = ResizeUnit.Pixel;
static ResizeSize()
=> _tokens = new Dictionary<string, string>
{
["$small$"] = Resources.Small,
["$medium$"] = Resources.Medium,
["$large$"] = Resources.Large,
["$phone$"] = Resources.Phone,
};
public ResizeSize(string name, ResizeFit fit, double width, double height, ResizeUnit unit)
{
Name = name;

View File

@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Windows.Media.Imaging;
@@ -18,7 +19,7 @@ using Newtonsoft.Json.Serialization;
namespace ImageResizer.Properties
{
[JsonObject(MemberSerialization.OptIn)]
public partial class Settings : IDataErrorInfo, INotifyPropertyChanged
public sealed partial class Settings : IDataErrorInfo, INotifyPropertyChanged
{
// Used to synchronize access to the settings.json file
private static Mutex _jsonMutex = new Mutex();
@@ -32,7 +33,6 @@ namespace ImageResizer.Properties
private PngInterlaceOption _pngInterlaceOption;
private TiffCompressOption _tiffCompressOption;
private string _fileName;
private ObservableCollection<ImageResizer.Models.ResizeSize> _sizes;
private bool _keepDateModified;
private System.Guid _fallbackEncoder;
private CustomSize _customSize;
@@ -92,7 +92,12 @@ namespace ImageResizer.Properties
}
string IDataErrorInfo.Error
=> string.Empty;
{
get
{
return string.Empty;
}
}
string IDataErrorInfo.this[string columnName]
{
@@ -105,7 +110,7 @@ namespace ImageResizer.Properties
if (JpegQualityLevel < 1 || JpegQualityLevel > 100)
{
return string.Format(Resources.ValueMustBeBetween, 1, 100);
return string.Format(CultureInfo.CurrentCulture, Resources.ValueMustBeBetween, 1, 100);
}
return string.Empty;
@@ -306,7 +311,7 @@ namespace ImageResizer.Properties
{
if (string.IsNullOrWhiteSpace(value))
{
throw new System.ArgumentNullException();
throw new System.ArgumentNullException(nameof(FileName));
}
_fileName = value;
@@ -315,15 +320,7 @@ namespace ImageResizer.Properties
}
[JsonProperty(PropertyName = "imageresizer_sizes")]
public ObservableCollection<ResizeSize> Sizes
{
get => _sizes;
set
{
_sizes = value;
NotifyPropertyChanged();
}
}
public ObservableCollection<ResizeSize> Sizes { get; private set; }
[JsonProperty(PropertyName = "imageresizer_keepDateModified")]
public bool KeepDateModified
@@ -423,12 +420,18 @@ namespace ImageResizer.Properties
PngInterlaceOption = jsonSettings.PngInterlaceOption;
TiffCompressOption = jsonSettings.TiffCompressOption;
FileName = jsonSettings.FileName;
Sizes = jsonSettings.Sizes;
KeepDateModified = jsonSettings.KeepDateModified;
FallbackEncoder = jsonSettings.FallbackEncoder;
CustomSize = jsonSettings.CustomSize;
SelectedSizeIndex = jsonSettings.SelectedSizeIndex;
if (jsonSettings.Sizes.Count > 0)
{
Sizes.Clear();
Sizes.AddRange(jsonSettings.Sizes);
}
});
_jsonMutex.ReleaseMutex();
}
}

View File

@@ -11,7 +11,7 @@ namespace ImageResizer.Utilities
{
// Win32 functions required for temporary workaround for issue #1273
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Naming used in Win32 dll")]
internal class Win32Helpers
internal class NativeMethods
{
[DllImport("user32.dll")]
internal static extern bool SetForegroundWindow(IntPtr hWnd);

View File

@@ -16,9 +16,7 @@ namespace ImageResizer.ViewModels
{
public class AdvancedViewModel : ViewModelBase
{
private static readonly IDictionary<Guid, string> _encoderMap;
static AdvancedViewModel()
private static Dictionary<Guid, string> InitEncoderMap()
{
var bmpCodec = new BmpBitmapEncoder().CodecInfo;
var gifCodec = new GifBitmapEncoder().CodecInfo;
@@ -27,7 +25,7 @@ namespace ImageResizer.ViewModels
var tiffCodec = new TiffBitmapEncoder().CodecInfo;
var wmpCodec = new WmpBitmapEncoder().CodecInfo;
_encoderMap = new Dictionary<Guid, string>
return new Dictionary<Guid, string>
{
[bmpCodec.ContainerFormat] = bmpCodec.FriendlyName,
[gifCodec.ContainerFormat] = gifCodec.FriendlyName,
@@ -45,17 +43,15 @@ namespace ImageResizer.ViewModels
Settings = settings;
}
public static IDictionary<Guid, string> EncoderMap
=> _encoderMap;
public static IDictionary<Guid, string> EncoderMap { get; } = InitEncoderMap();
public Settings Settings { get; }
public string Version
public static string Version
=> typeof(AdvancedViewModel).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion;
public IEnumerable<Guid> Encoders
=> _encoderMap.Keys;
public static IEnumerable<Guid> Encoders => EncoderMap.Keys;
public ICommand RemoveSizeCommand { get; }

View File

@@ -29,7 +29,10 @@ namespace ImageResizer.ViewModels
_mainView = mainView;
Settings = settings;
settings.CustomSize.PropertyChanged += (sender, e) => settings.SelectedSize = (CustomSize)sender;
if (settings != null)
{
settings.CustomSize.PropertyChanged += (sender, e) => settings.SelectedSize = (CustomSize)sender;
}
ResizeCommand = new RelayCommand(Resize);
CancelCommand = new RelayCommand(Cancel);

View File

@@ -45,7 +45,7 @@ namespace ImageResizer.ViewModels
{
if (_batch.Files.Count == 0)
{
_batch.Files.AddRange(view.OpenPictureFiles());
_batch.Files.AddRange(view?.OpenPictureFiles());
}
CurrentPage = new InputViewModel(_settings, this, view, _batch);

View File

@@ -15,7 +15,7 @@ using ImageResizer.Views;
namespace ImageResizer.ViewModels
{
public class ProgressViewModel : ViewModelBase
public class ProgressViewModel : ViewModelBase, IDisposable
{
private readonly MainViewModel _mainViewModel;
private readonly ResizeBatch _batch;
@@ -25,6 +25,7 @@ namespace ImageResizer.ViewModels
private double _progress;
private TimeSpan _timeRemaining;
private bool disposedValue;
public ProgressViewModel(
ResizeBatch batch,
@@ -56,37 +57,61 @@ namespace ImageResizer.ViewModels
public ICommand StopCommand { get; }
public void Start()
=> Task.Factory.StartNew(
() =>
{
_ = Task.Factory.StartNew(StartExecutingWork, _cancellationTokenSource.Token, TaskCreationOptions.None, TaskScheduler.Current);
}
private void StartExecutingWork()
{
_stopwatch.Restart();
var errors = _batch.Process(
(completed, total) =>
{
_stopwatch.Restart();
var errors = _batch.Process(
_cancellationTokenSource.Token,
(completed, total) =>
{
var progress = completed / total;
Progress = progress;
_mainViewModel.Progress = progress;
var progress = completed / total;
Progress = progress;
_mainViewModel.Progress = progress;
TimeRemaining = _stopwatch.Elapsed.Multiply((total - completed) / completed);
});
if (errors.Any())
{
_mainViewModel.Progress = 0;
_mainViewModel.CurrentPage = new ResultsViewModel(_mainView, errors);
}
else
{
_mainView.Close();
}
TimeRemaining = _stopwatch.Elapsed.Multiply((total - completed) / completed);
},
_cancellationTokenSource.Token);
if (errors.Any())
{
_mainViewModel.Progress = 0;
_mainViewModel.CurrentPage = new ResultsViewModel(_mainView, errors);
}
else
{
_mainView.Close();
}
}
public void Stop()
{
_cancellationTokenSource.Cancel();
_mainView.Close();
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_cancellationTokenSource.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -15,7 +15,7 @@ namespace ImageResizer.Views
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> AdvancedViewModel.EncoderMap.TryGetValue((Guid)value, out var result)
? result
: value.ToString();
: value?.ToString();
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotImplementedException();

View File

@@ -15,12 +15,12 @@ namespace ImageResizer.Views
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var type = value.GetType();
var type = value?.GetType();
var builder = new StringBuilder();
builder
.Append(type.Name)
.Append("_")
.Append('_')
.Append(Enum.GetName(type, value));
var toLower = false;
@@ -31,15 +31,15 @@ namespace ImageResizer.Views
else if (parameter != null)
{
builder
.Append("_")
.Append('_')
.Append(parameter);
}
var targetValue = Resources.ResourceManager.GetString(builder.ToString());
var targetValue = Resources.ResourceManager.GetString(builder.ToString(), culture);
if (toLower)
{
targetValue = targetValue.ToLower();
targetValue = targetValue.ToLower(culture);
}
return targetValue;

View File

@@ -39,8 +39,7 @@ namespace ImageResizer.Views
return openFileDialog.FileNames;
}
public void ShowAdvanced(AdvancedViewModel viewModel)
=> viewModel.Close(new AdvancedWindow(viewModel).ShowDialog() == true);
public void ShowAdvanced(AdvancedViewModel viewModel) => viewModel?.Close(new AdvancedWindow(viewModel).ShowDialog() == true);
void IMainView.Close()
=> Dispatcher.Invoke((Action)Close);

View File

@@ -15,7 +15,7 @@ namespace ImageResizer.Views
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var output = Resources.ResourceManager.GetString(Enum.GetName(typeof(ResizeUnit), value));
var output = Resources.ResourceManager.GetString(Enum.GetName(typeof(ResizeUnit), value), culture);
if ((string)parameter == "ToLower")
{

View File

@@ -15,7 +15,8 @@ namespace ImageResizer.Views
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> Resources.ResourceManager.GetString(
"TiffCompressOption_" + Enum.GetName(typeof(TiffCompressOption), value));
"TiffCompressOption_" + Enum.GetName(typeof(TiffCompressOption), value),
culture);
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotImplementedException();

View File

@@ -36,7 +36,7 @@ namespace ImageResizer.Views
return string.Format(
culture,
Resources.ResourceManager.GetString(builder.ToString()),
Resources.ResourceManager.GetString(builder.ToString(), culture),
timeRemaining.Hours,
timeRemaining.Minutes,
timeRemaining.Seconds);