Implementing internal message bus

This commit is contained in:
Anton Kasyanov
2018-02-05 01:32:30 +02:00
parent bb4b61c55f
commit 6bf73ad501
27 changed files with 245 additions and 109 deletions

View File

@@ -21,13 +21,35 @@ namespace EveOPreview
public void Register<TService>()
{
this._container.Register<TService>();
if (!typeof(TService).IsInterface)
{
this._container.Register<TService>(new PerContainerLifetime());
return;
}
foreach (Type implementationType in typeof(TService).Assembly.DefinedTypes)
{
if (!implementationType.IsClass || implementationType.IsAbstract)
{
continue;
}
if (!typeof(TService).IsAssignableFrom(implementationType))
{
continue;
}
this._container.Register(typeof(TService), implementationType, new PerContainerLifetime());
break;
}
}
public void Register<TService, TImplementation>()
where TImplementation : TService
{
this._container.Register<TService, TImplementation>();
this._container.Register<TService, TImplementation>(new PerContainerLifetime());
}
public void Register<TService, TArgument>(Expression<Func<TArgument, TService>> factory)

View File

@@ -8,12 +8,12 @@ namespace EveOPreview.Configuration
private const string ConfigurationFileName = "EVE-O Preview.json";
private readonly IAppConfig _appConfig;
private readonly IThumbnailConfig _thumbnailConfig;
private readonly IThumbnailConfiguration _thumbnailConfiguration;
public ConfigurationStorage(IAppConfig appConfig, IThumbnailConfig thumbnailConfig)
public ConfigurationStorage(IAppConfig appConfig, IThumbnailConfiguration thumbnailConfiguration)
{
this._appConfig = appConfig;
this._thumbnailConfig = thumbnailConfig;
this._thumbnailConfiguration = thumbnailConfiguration;
}
public void Load()
@@ -27,15 +27,15 @@ namespace EveOPreview.Configuration
string rawData = File.ReadAllText(filename);
JsonConvert.PopulateObject(rawData, this._thumbnailConfig);
JsonConvert.PopulateObject(rawData, this._thumbnailConfiguration);
// Validate data after loading it
this._thumbnailConfig.ApplyRestrictions();
this._thumbnailConfiguration.ApplyRestrictions();
}
public void Save()
{
string rawData = JsonConvert.SerializeObject(this._thumbnailConfig, Formatting.Indented);
string rawData = JsonConvert.SerializeObject(this._thumbnailConfiguration, Formatting.Indented);
File.WriteAllText(this.GetConfigFileName(), rawData);
}

View File

@@ -3,10 +3,7 @@ using System.Windows.Forms;
namespace EveOPreview.Configuration
{
/// <summary>
/// Thumbnails Manager configuration
/// </summary>
public interface IThumbnailConfig
public interface IThumbnailConfiguration
{
bool MinimizeToTray { get; set; }
int ThumbnailRefreshPeriod { get; set; }

View File

@@ -5,9 +5,9 @@ using Newtonsoft.Json;
namespace EveOPreview.Configuration
{
class ThumbnailConfig : IThumbnailConfig
class ThumbnailConfiguration : IThumbnailConfiguration
{
public ThumbnailConfig()
public ThumbnailConfiguration()
{
this.MinimizeToTray = false;
this.ThumbnailRefreshPeriod = 500;
@@ -173,12 +173,12 @@ namespace EveOPreview.Configuration
/// </summary>
public void ApplyRestrictions()
{
this.ThumbnailRefreshPeriod = ThumbnailConfig.ApplyRestrictions(this.ThumbnailRefreshPeriod, 300, 1000);
this.ThumbnailSize = new Size(ThumbnailConfig.ApplyRestrictions(this.ThumbnailSize.Width, this.ThumbnailMinimumSize.Width, this.ThumbnailMaximumSize.Width),
ThumbnailConfig.ApplyRestrictions(this.ThumbnailSize.Height, this.ThumbnailMinimumSize.Height, this.ThumbnailMaximumSize.Height));
this.ThumbnailOpacity = ThumbnailConfig.ApplyRestrictions((int)(this.ThumbnailOpacity * 100.00), 20, 100) / 100.00;
this.ThumbnailZoomFactor = ThumbnailConfig.ApplyRestrictions(this.ThumbnailZoomFactor, 2, 10);
this.ActiveClientHighlightThickness = ThumbnailConfig.ApplyRestrictions(this.ActiveClientHighlightThickness, 1, 6);
this.ThumbnailRefreshPeriod = ThumbnailConfiguration.ApplyRestrictions(this.ThumbnailRefreshPeriod, 300, 1000);
this.ThumbnailSize = new Size(ThumbnailConfiguration.ApplyRestrictions(this.ThumbnailSize.Width, this.ThumbnailMinimumSize.Width, this.ThumbnailMaximumSize.Width),
ThumbnailConfiguration.ApplyRestrictions(this.ThumbnailSize.Height, this.ThumbnailMinimumSize.Height, this.ThumbnailMaximumSize.Height));
this.ThumbnailOpacity = ThumbnailConfiguration.ApplyRestrictions((int)(this.ThumbnailOpacity * 100.00), 20, 100) / 100.00;
this.ThumbnailZoomFactor = ThumbnailConfiguration.ApplyRestrictions(this.ThumbnailZoomFactor, 2, 10);
this.ActiveClientHighlightThickness = ThumbnailConfiguration.ApplyRestrictions(this.ActiveClientHighlightThickness, 1, 6);
}
private static int ApplyRestrictions(int value, int minimum, int maximum)

View File

@@ -108,21 +108,24 @@
<Compile Include="Configuration\AppConfig.cs" />
<Compile Include="Configuration\ConfigurationStorage.cs" />
<Compile Include="Configuration\IAppConfig.cs" />
<Compile Include="Configuration\IThumbnailConfig.cs" />
<Compile Include="Configuration\IThumbnailConfiguration.cs" />
<Compile Include="Configuration\ZoomAnchor.cs" />
<Compile Include="DwmAPI\Implementation\DwmThumbnail.cs" />
<Compile Include="DwmAPI\Interface\IDwmThumbnail.cs" />
<Compile Include="DwmAPI\Interface\InteropConstants.cs" />
<Compile Include="DwmAPI\Interop\DWM_BLURBEHIND.cs" />
<Compile Include="DwmAPI\Interop\DWM_THUMBNAIL_PROPERTIES.cs" />
<Compile Include="DwmAPI\Interop\DWM_TNP_CONSTANTS.cs" />
<Compile Include="DwmAPI\Interface\IWindowManager.cs" />
<Compile Include="DwmAPI\Interop\MARGINS.cs" />
<Compile Include="DwmAPI\Interop\RECT.cs" />
<Compile Include="WindowManager\Implementation\DwmThumbnail.cs" />
<Compile Include="WindowManager\Interface\IDwmThumbnail.cs" />
<Compile Include="WindowManager\Interface\InteropConstants.cs" />
<Compile Include="WindowManager\Interop\DWM_BLURBEHIND.cs" />
<Compile Include="WindowManager\Interop\DWM_THUMBNAIL_PROPERTIES.cs" />
<Compile Include="WindowManager\Interop\DWM_TNP_CONSTANTS.cs" />
<Compile Include="WindowManager\Interface\IWindowManager.cs" />
<Compile Include="WindowManager\Interop\MARGINS.cs" />
<Compile Include="WindowManager\Interop\RECT.cs" />
<Compile Include="Configuration\ClientLayout.cs" />
<Compile Include="ApplicationBase\IPresenter.cs" />
<Compile Include="DwmAPI\Implementation\WindowManager.cs" />
<Compile Include="DwmAPI\Interop\User32NativeMethods.cs" />
<Compile Include="WindowManager\Implementation\WindowManager.cs" />
<Compile Include="WindowManager\Interop\User32NativeMethods.cs" />
<Compile Include="Mediator\Implementation\Mediator.cs" />
<Compile Include="Mediator\Interface\IMediator.cs" />
<Compile Include="Mediator\Interface\INotification.cs" />
<Compile Include="Presentation\MainPresenter.cs" />
<Compile Include="Presentation\ViewCloseRequest.cs" />
<Compile Include="Presentation\ViewZoomAnchorConverter.cs" />
@@ -135,7 +138,7 @@
<Compile Include="UI\Interface\IThumbnailDescriptionView.cs" />
<Compile Include="UI\Interface\IThumbnailDescriptionViewFactory.cs" />
<Compile Include="UI\Interface\ViewZoomAnchor.cs" />
<Compile Include="Configuration\ThumbnailConfig.cs" />
<Compile Include="Configuration\ThumbnailConfiguration.cs" />
<Compile Include="Hotkeys\HotkeyHandler.cs" />
<Compile Include="Hotkeys\HotkeyHandlerNativeMethods.cs" />
<Compile Include="UI\Implementation\MainForm.cs">
@@ -183,7 +186,7 @@
<Compile Include="UI\Implementation\ThumbnailView.Designer.cs">
<DependentUpon>ThumbnailView.cs</DependentUpon>
</Compile>
<Compile Include="DwmAPI\Interop\DwmApiNativeMethods.cs" />
<Compile Include="WindowManager\Interop\DwmApiNativeMethods.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />

View File

@@ -2,7 +2,7 @@ using System;
using System.Windows.Forms;
using System.ComponentModel;
namespace EveOPreview.UI
namespace EveOPreview.UI.Hotkeys
{
class HotkeyHandler : IMessageFilter, IDisposable
{

View File

@@ -1,7 +1,7 @@
using System;
using System.Runtime.InteropServices;
namespace EveOPreview.UI
namespace EveOPreview.UI.Hotkeys
{
static class HotkeyHandlerNativeMethods
{

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace EveOPreview.Mediator.Implementation
{
class Mediator : IMediator
{
#region Private fields
private readonly ReaderWriterLockSlim _lock;
private readonly IDictionary<Type, IList> _handlers;
#endregion
public Mediator()
{
this._lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
this._handlers = new Dictionary<Type, IList>();
}
public void Subscribe<T>(Action<T> handler)
where T : INotification
{
this._lock.EnterWriteLock();
try
{
IList handlers;
if (!this._handlers.TryGetValue(typeof(T), out handlers))
{
handlers = new List<Action<T>>();
this._handlers.Add(typeof(T), handlers);
}
handlers.Add(handler);
}
finally
{
this._lock.ExitWriteLock();
}
}
public void Unsubscribe<T>(Action<T> handler)
where T : INotification
{
this._lock.EnterWriteLock();
try
{
this._handlers.TryGetValue(typeof(T), out var handlers);
handlers?.Remove(handler);
}
finally
{
this._lock.ExitWriteLock();
}
}
public void Publish<T>(T notification)
where T : INotification
{
// Empty notifications are silently swallowed
if (notification.IsEmpty())
{
return;
}
IList<Action<T>> handlers;
this._lock.EnterReadLock();
try
{
if (!this._handlers.TryGetValue(typeof(T), out var untypedHandlers))
{
return;
}
// Clone the list to minimize lock time
// and possible deadlock issues (f.e. one of subscribers could raise an event ar add/remove subsctibers etc)
handlers = new List<Action<T>>((IList<Action<T>>)untypedHandlers);
}
finally
{
this._lock.ExitReadLock();
}
foreach (var handler in handlers)
{
handler.Invoke(notification);
}
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
namespace EveOPreview.Mediator
{
/// <summary>
/// Message dispatcher.
/// Consider this as a very simple message bus
/// </summary>
public interface IMediator
{
void Subscribe<T>(Action<T> handler)
where T : INotification;
void Unsubscribe<T>(Action<T> handler)
where T : INotification;
void Publish<T>(T notification)
where T : INotification;
}
}

View File

@@ -0,0 +1,10 @@
namespace EveOPreview.Mediator
{
/// <summary>
/// Base class for all Mediator notifications
/// </summary>
public interface INotification
{
bool IsEmpty();
}
}

View File

@@ -13,7 +13,7 @@ namespace EveOPreview.UI
#endregion
#region Private fields
private readonly IThumbnailConfig _configuration;
private readonly IThumbnailConfiguration _configuration;
private readonly IConfigurationStorage _configurationStorage;
private readonly IThumbnailDescriptionViewFactory _thumbnailDescriptionViewFactory;
private readonly IDictionary<IntPtr, IThumbnailDescriptionView> _thumbnailDescriptionViews;
@@ -22,7 +22,7 @@ namespace EveOPreview.UI
private bool _exitApplication;
#endregion
public MainPresenter(IApplicationController controller, IMainView view, IThumbnailConfig configuration, IConfigurationStorage configurationStorage,
public MainPresenter(IApplicationController controller, IMainView view, IThumbnailConfiguration configuration, IConfigurationStorage configurationStorage,
IThumbnailManager thumbnailManager, IThumbnailDescriptionViewFactory thumbnailDescriptionViewFactory)
: base(controller, view)
{

View File

@@ -4,6 +4,7 @@ using System.Diagnostics;
using System.Drawing;
using System.Windows.Threading;
using EveOPreview.Configuration;
using EveOPreview.WindowManager;
namespace EveOPreview.UI
{
@@ -19,7 +20,7 @@ namespace EveOPreview.UI
#region Private fields
private readonly IWindowManager _windowManager;
private readonly IThumbnailConfig _configuration;
private readonly IThumbnailConfiguration _configuration;
private readonly DispatcherTimer _thumbnailUpdateTimer;
private readonly IThumbnailViewFactory _thumbnailViewFactory;
private readonly Dictionary<IntPtr, IThumbnailView> _thumbnailViews;
@@ -31,7 +32,7 @@ namespace EveOPreview.UI
private bool _isHoverEffectActive;
#endregion
public ThumbnailManager(IWindowManager windowManager, IThumbnailConfig configuration, IThumbnailViewFactory factory)
public ThumbnailManager(IWindowManager windowManager, IThumbnailConfiguration configuration, IThumbnailViewFactory factory)
{
this._windowManager = windowManager;
this._configuration = configuration;

View File

@@ -2,18 +2,19 @@ using System;
using System.Threading;
using System.Windows.Forms;
using EveOPreview.Configuration;
using EveOPreview.Mediator;
using EveOPreview.UI;
using EveOPreview.WindowManager;
namespace EveOPreview
{
static class Program
{
private static string MutexName = "EVE-O Preview Single Instance Mutex";
private static string ConfigParameterName = "--config:";
/// <summary>The main entry point for the application.</summary>
[STAThread]
static void Main(string[] args)
static void Main()
{
// The very usual Mutex-based single-instance screening
// 'token' variable is used to store reference to the instance Mutex
@@ -30,44 +31,12 @@ namespace EveOPreview
ExceptionHandler handler = new ExceptionHandler();
handler.SetupExceptionHandlers();
Program.InitializeWinFormsGui();
IApplicationController controller = Program.InitializeApplicationController();
Program.SetupApplicationConttroller(controller, Program.GetCustomConfigFile(args));
Program.InitializeWinForms();
controller.Run<MainPresenter>();
}
private static string GetCustomConfigFile(string[] args)
{
// Parse startup parameters
// Simple approach is used because something like NParams would be an overkill here
string configFile = null;
foreach (string arg in args)
{
if ((arg.Length <= Program.ConfigParameterName.Length) || !arg.StartsWith(Program.ConfigParameterName, StringComparison.Ordinal))
{
continue;
}
configFile = arg.Substring(Program.ConfigParameterName.Length);
break;
}
if (string.IsNullOrEmpty(configFile))
{
return "";
}
// One more check to drop trailing "
if ((configFile.Length > 3) && (configFile[0] == '"') && (configFile[configFile.Length - 1] == '"'))
{
configFile = configFile.Substring(1, configFile.Length - 2);
}
return configFile;
}
private static object GetInstanceToken()
{
// The code might look overcomplicated here for a single Mutex operation
@@ -92,7 +61,7 @@ namespace EveOPreview
}
}
private static void InitializeWinFormsGui()
private static void InitializeWinForms()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
@@ -102,28 +71,30 @@ namespace EveOPreview
{
IIocContainer container = new LightInjectContainer();
// Singleton registration is used for services
// Low-level services
container.Register<IMediator>();
container.Register<IWindowManager>();
// Configuration services
container.Register<IConfigurationStorage>();
container.Register<IAppConfig>();
container.Register<IThumbnailConfiguration>();
// Application services
container.Register<IThumbnailManager>();
container.Register<IThumbnailViewFactory>();
container.Register<IThumbnailDescriptionViewFactory>();
IApplicationController controller = new ApplicationController(container);
// UI classes
IApplicationController controller = new ApplicationController(container)
.RegisterView<IMainView, MainForm>()
controller.RegisterView<IMainView, MainForm>()
.RegisterView<IThumbnailView, ThumbnailView>()
.RegisterView<IThumbnailDescriptionView, ThumbnailDescriptionView>()
.RegisterInstance(new ApplicationContext());
// Application services
controller.RegisterService<IThumbnailManager, ThumbnailManager>()
.RegisterService<IThumbnailViewFactory, ThumbnailViewFactory>()
.RegisterService<IThumbnailDescriptionViewFactory, ThumbnailDescriptionViewFactory>()
.RegisterService<IConfigurationStorage, ConfigurationStorage>()
.RegisterService<IWindowManager, EveOPreview.DwmAPI.WindowManager>()
.RegisterInstance<IAppConfig>(new AppConfig())
.RegisterInstance<IThumbnailConfig>(new ThumbnailConfig());
return controller;
}
private static void SetupApplicationConttroller(IApplicationController controller, string configFile)
{
controller.Create<IAppConfig>().ConfigFileName = configFile;
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Windows.Forms;
using EveOPreview.WindowManager;
namespace EveOPreview.UI
{

View File

@@ -2,7 +2,8 @@ using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using EveOPreview.DwmAPI;
using EveOPreview.UI.Hotkeys;
using EveOPreview.WindowManager;
namespace EveOPreview.UI
{
@@ -475,8 +476,7 @@ namespace EveOPreview.UI
#region Thumbnail management
private void RegisterThumbnail()
{
this._thumbnail = new DwmThumbnail(this._windowManager);
this._thumbnail.Register(this.Handle, this.Id);
this._thumbnail = this._windowManager.RegisterThumbnail(this.Handle, this.Id);
}
private void UpdateThumbnail()

View File

@@ -1,7 +1,6 @@
using System;
using EveOPreview.DwmInterop;
namespace EveOPreview.DwmAPI
namespace EveOPreview.WindowManager.Implementation
{
class DwmThumbnail : IDwmThumbnail
{

View File

@@ -1,7 +1,6 @@
using System;
using EveOPreview.DwmInterop;
namespace EveOPreview.DwmAPI
namespace EveOPreview.WindowManager.Implementation
{
class WindowManager : IWindowManager
{
@@ -48,5 +47,18 @@ namespace EveOPreview.DwmAPI
right = windowRectangle.Right;
bottom = windowRectangle.Bottom;
}
public bool IsWindowMinimized(IntPtr handle)
{
return User32NativeMethods.IsIconic(handle);
}
public IDwmThumbnail RegisterThumbnail(IntPtr destination, IntPtr source)
{
IDwmThumbnail thumbnail = new DwmThumbnail(this);
thumbnail.Register(destination, source);
return thumbnail;
}
}
}

View File

@@ -1,6 +1,6 @@
using System;
namespace EveOPreview
namespace EveOPreview.WindowManager
{
public interface IDwmThumbnail
{

View File

@@ -1,6 +1,6 @@
using System;
namespace EveOPreview
namespace EveOPreview.WindowManager
{
public interface IWindowManager
{
@@ -13,5 +13,8 @@ namespace EveOPreview
void MoveWindow(IntPtr handle, int left, int top, int width, int height);
void GetWindowCoordinates(IntPtr handle, out int left, out int top, out int right, out int bottom);
bool IsWindowMinimized(IntPtr handle);
IDwmThumbnail RegisterThumbnail(IntPtr destination, IntPtr source);
}
}

View File

@@ -1,6 +1,6 @@
using System;
namespace EveOPreview
namespace EveOPreview.WindowManager
{
public static class InteropConstants
{

View File

@@ -1,7 +1,7 @@
using System;
using System.Runtime.InteropServices;
namespace EveOPreview.DwmInterop
namespace EveOPreview.WindowManager.Implementation
{
[StructLayout(LayoutKind.Sequential)]
class DWM_BLURBEHIND

View File

@@ -1,6 +1,6 @@
using System.Runtime.InteropServices;
namespace EveOPreview.DwmInterop
namespace EveOPreview.WindowManager.Implementation
{
[StructLayout(LayoutKind.Sequential)]
class DWM_THUMBNAIL_PROPERTIES

View File

@@ -1,4 +1,4 @@
namespace EveOPreview.DwmInterop
namespace EveOPreview.WindowManager.Implementation
{
static class DWM_TNP_CONSTANTS
{

View File

@@ -2,7 +2,7 @@ using System;
using System.Runtime.InteropServices;
using System.Drawing;
namespace EveOPreview.DwmInterop
namespace EveOPreview.WindowManager.Implementation
{
static class DwmApiNativeMethods
{

View File

@@ -1,6 +1,6 @@
using System.Runtime.InteropServices;
namespace EveOPreview.DwmInterop
namespace EveOPreview.WindowManager.Implementation
{
[StructLayout(LayoutKind.Sequential)]
class MARGINS

View File

@@ -1,6 +1,6 @@
using System.Runtime.InteropServices;
namespace EveOPreview.DwmInterop
namespace EveOPreview.WindowManager.Implementation
{
[StructLayout(LayoutKind.Sequential)]
struct RECT

View File

@@ -1,7 +1,7 @@
using System;
using System.Runtime.InteropServices;
namespace EveOPreview.DwmInterop
namespace EveOPreview.WindowManager.Implementation
{
static class User32NativeMethods
{
@@ -28,5 +28,12 @@ namespace EveOPreview.DwmInterop
[DllImport("user32.dll")]
public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool IsZoomed(IntPtr hWnd);
}
}