diff --git a/App.xaml b/App.xaml index 250340b..bffea81 100644 --- a/App.xaml +++ b/App.xaml @@ -1,4 +1,12 @@ - + + + + + + \ No newline at end of file diff --git a/App.xaml.cs b/App.xaml.cs index 27024de..9427ccd 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,8 +1,44 @@ -using System.Windows; +using System.Linq; +using System.Reflection; +using System.Windows; +using PettingZoo.Model; +using PettingZoo.View; +using SimpleInjector; namespace PettingZoo { public partial class App : Application { + public void ApplicationStartup(object sender, StartupEventArgs e) + { + var container = Bootstrap(); + RunApplication(container); + } + + + private static Container Bootstrap() + { + var container = new Container(); + + container.Register(); + container.Register(); + + // Automatically register all Window and BaseViewModel descendants + foreach (var type in Assembly.GetExecutingAssembly().GetExportedTypes() + .Where(t => t.IsSubclassOf(typeof(System.Windows.Window)) || + t.IsSubclassOf(typeof(Infrastructure.BaseViewModel)))) + { + container.Register(type); + } + + return container; + } + + + private static void RunApplication(Container container) + { + var mainWindow = container.GetInstance(); + mainWindow.Show(); + } } } diff --git a/Infrastructure/GridLayout.cs b/Infrastructure/GridLayout.cs new file mode 100644 index 0000000..4555054 --- /dev/null +++ b/Infrastructure/GridLayout.cs @@ -0,0 +1,101 @@ +using System.Windows; +using System.Windows.Controls; + +namespace PettingZoo.Infrastructure +{ + // Source: http://daniel-albuschat.blogspot.nl/2011/07/gridlayout-for-wpf-escape-margin-hell.html + + // The GridLayout is a special Panel that can be used exactly like the Grid Panel, except that it + // defines a new property ChildMargin. ChildMargin's left, top, right and bottom margins will be applied + // to all children in a way that the children will have a vertical space of ChildMargin.Top+ChildMargin.Bottom + // and a horizontal space of ChildMargin.Left+ChildMargin.Right between them. + // However, there is no margin for the borders of the internal widget, so that the GridLayout itself can be + // aligned to another element without a margin. + // It's best to have a look at TestWindow, which effectively tests all possible alignments of children. + public class GridLayout : Grid + { + public static readonly DependencyProperty ChildMarginProperty = DependencyProperty.Register( + "ChildMargin", + typeof (Thickness), + typeof (GridLayout), + new FrameworkPropertyMetadata(new Thickness(5)) + { + AffectsArrange = true, + AffectsMeasure = true + }); + + // The child margin defines a margin that will be automatically applied to all children of this Grid. + // However, the children at the edges will have the respective margins remove. E.g. the leftmost children will have + // a Margin.Left of 0 and the children in the first row will have a Margin.Top of 0. + // The margins that are not set to 0 are set to half the ChildMargin's value, since it's neighbour will also apply it, + // effectively doubling it. + + public Thickness ChildMargin + { + get { return (Thickness) GetValue(ChildMarginProperty); } + set + { + SetValue(ChildMarginProperty, value); + UpdateChildMargins(); + } + } + + // UpdateChildMargin first finds out what's the rightmost column and bottom row and then applies + // the correct margins to all children. + + public void UpdateChildMargins() + { + int maxColumn = 0; + int maxRow = 0; + foreach (UIElement element in InternalChildren) + { + int row = GetRow(element); + int column = GetColumn(element); + if (row > maxRow) + maxRow = row; + if (column > maxColumn) + maxColumn = column; + } + foreach (UIElement element in InternalChildren) + { + FrameworkElement fe = element as FrameworkElement; + if (null != fe) + { + int row = GetRow(fe); + int column = GetColumn(fe); + double factorLeft = 0.5; + double factorTop = 0.5; + double factorRight = 0.5; + double factorBottom = 0.5; + // Top row - no top margin + if (row == 0) + factorTop = 0; + // Bottom row - no bottom margin + if (row == maxRow) + factorBottom = 0; + // Leftmost column = no left margin + if (column == 0) + factorLeft = 0; + // Rightmost column - no right margin + if (column == maxColumn) + factorRight = 0; + fe.Margin = new Thickness(ChildMargin.Left*factorLeft, + ChildMargin.Top*factorTop, + ChildMargin.Right*factorRight, + ChildMargin.Bottom*factorBottom); + } + } + } + + // We change all children's margins in MeasureOverride, since this is called right before + // the layouting takes place. I was first skeptical to do this here, because I thought changing + // the margin will trigger a LayoutUpdate, which in turn would lead to an endless recursion, + // but apparantly WPF takes care of this. + + protected override Size MeasureOverride(Size availableSize) + { + UpdateChildMargins(); + return base.MeasureOverride(availableSize); + } + } +} \ No newline at end of file diff --git a/Infrastructure/PasswordBoxAssistant.cs b/Infrastructure/PasswordBoxAssistant.cs new file mode 100644 index 0000000..698c6f1 --- /dev/null +++ b/Infrastructure/PasswordBoxAssistant.cs @@ -0,0 +1,112 @@ +using System.Windows; +using System.Windows.Controls; + +namespace PettingZoo.Infrastructure +{ + // Source: http://blog.functionalfun.net/2008/06/wpf-passwordbox-and-data-binding.html + public static class PasswordBoxAssistant + { + public static readonly DependencyProperty BoundPassword = + DependencyProperty.RegisterAttached("BoundPassword", typeof (string), typeof (PasswordBoxAssistant), + new PropertyMetadata(string.Empty, OnBoundPasswordChanged)); + + public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached( + "BindPassword", typeof (bool), typeof (PasswordBoxAssistant), + new PropertyMetadata(false, OnBindPasswordChanged)); + + private static readonly DependencyProperty UpdatingPassword = + DependencyProperty.RegisterAttached("UpdatingPassword", typeof (bool), typeof (PasswordBoxAssistant), + new PropertyMetadata(false)); + + private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + PasswordBox box = d as PasswordBox; + + // only handle this event when the property is attached to a PasswordBox + // and when the BindPassword attached property has been set to true + if (d == null || !GetBindPassword(d)) + { + return; + } + + // avoid recursive updating by ignoring the box's changed event + box.PasswordChanged -= HandlePasswordChanged; + + string newPassword = (string) e.NewValue; + + if (!GetUpdatingPassword(box)) + { + box.Password = newPassword; + } + + box.PasswordChanged += HandlePasswordChanged; + } + + private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) + { + // when the BindPassword attached property is set on a PasswordBox, + // start listening to its PasswordChanged event + + PasswordBox box = dp as PasswordBox; + + if (box == null) + { + return; + } + + bool wasBound = (bool) (e.OldValue); + bool needToBind = (bool) (e.NewValue); + + if (wasBound) + { + box.PasswordChanged -= HandlePasswordChanged; + } + + if (needToBind) + { + box.PasswordChanged += HandlePasswordChanged; + } + } + + private static void HandlePasswordChanged(object sender, RoutedEventArgs e) + { + PasswordBox box = sender as PasswordBox; + + // set a flag to indicate that we're updating the password + SetUpdatingPassword(box, true); + // push the new password into the BoundPassword property + SetBoundPassword(box, box.Password); + SetUpdatingPassword(box, false); + } + + public static void SetBindPassword(DependencyObject dp, bool value) + { + dp.SetValue(BindPassword, value); + } + + public static bool GetBindPassword(DependencyObject dp) + { + return (bool) dp.GetValue(BindPassword); + } + + public static string GetBoundPassword(DependencyObject dp) + { + return (string) dp.GetValue(BoundPassword); + } + + public static void SetBoundPassword(DependencyObject dp, string value) + { + dp.SetValue(BoundPassword, value); + } + + private static bool GetUpdatingPassword(DependencyObject dp) + { + return (bool) dp.GetValue(UpdatingPassword); + } + + private static void SetUpdatingPassword(DependencyObject dp, bool value) + { + dp.SetValue(UpdatingPassword, value); + } + } +} diff --git a/Model/IConnection.cs b/Model/IConnection.cs index ac13c83..fbca3a0 100644 --- a/Model/IConnection.cs +++ b/Model/IConnection.cs @@ -1,6 +1,8 @@ -namespace PettingZoo.Model +using System; + +namespace PettingZoo.Model { - public interface IConnection + public interface IConnection : IDisposable { } } diff --git a/Model/RabbitMQClientConnection.cs b/Model/RabbitMQClientConnection.cs index 4a7e2cd..36eeaa0 100644 --- a/Model/RabbitMQClientConnection.cs +++ b/Model/RabbitMQClientConnection.cs @@ -6,5 +6,10 @@ { } + + + public void Dispose() + { + } } } diff --git a/PettingZoo.csproj b/PettingZoo.csproj index 4baf7a8..3aca9b9 100644 --- a/PettingZoo.csproj +++ b/PettingZoo.csproj @@ -50,7 +50,8 @@ 4 - PettingZoo.Program + + @@ -76,6 +77,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -90,15 +95,16 @@ + + - - - + + ConnectionWindow.xaml @@ -122,7 +128,7 @@ True - ResXFileCodeGenerator + PublicResXFileCodeGenerator Resources.Designer.cs diff --git a/Program.cs b/Program.cs deleted file mode 100644 index 7ad652d..0000000 --- a/Program.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using PettingZoo.Model; -using PettingZoo.View; -using SimpleInjector; - -namespace PettingZoo -{ - static class Program - { - [STAThread] - static void Main() - { - var container = Bootstrap(); - RunApplication(container); - } - - - private static Container Bootstrap() - { - var container = new Container(); - - container.Register(); - container.Register(); - - // Automatically register all Window and BaseViewModel descendants - foreach (var type in Assembly.GetExecutingAssembly().GetExportedTypes() - .Where(t => t.IsSubclassOf(typeof(System.Windows.Window)) || - t.IsSubclassOf(typeof(Infrastructure.BaseViewModel)))) - { - container.Register(type); - } - - return container; - } - - - private static void RunApplication(Container container) - { - var app = new App(); - var mainWindow = container.GetInstance(); - app.Run(mainWindow); - } - } -} diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index 087ac86..9dd44d7 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -8,10 +8,10 @@ // //------------------------------------------------------------------------------ -namespace PettingZoo.Properties -{ - - +namespace PettingZoo.Properties { + using System; + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,50 +22,141 @@ namespace PettingZoo.Properties [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.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; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { + 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 - { - get - { - if ((resourceMan == null)) - { + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { + public static global::System.Globalization.CultureInfo Culture { + get { return resourceCulture; } - set - { + set { resourceCulture = value; } } + + /// + /// Looks up a localized string similar to Cancel. + /// + public static string ButtonCancel { + get { + return ResourceManager.GetString("ButtonCancel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to OK. + /// + public static string ButtonOK { + get { + return ResourceManager.GetString("ButtonOK", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Exchange:. + /// + public static string ConnectionExchange { + get { + return ResourceManager.GetString("ConnectionExchange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Host:. + /// + public static string ConnectionHost { + get { + return ResourceManager.GetString("ConnectionHost", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Password:. + /// + public static string ConnectionPassword { + get { + return ResourceManager.GetString("ConnectionPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Port:. + /// + public static string ConnectionPort { + get { + return ResourceManager.GetString("ConnectionPort", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Routing key:. + /// + public static string ConnectionRoutingKey { + get { + return ResourceManager.GetString("ConnectionRoutingKey", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Username:. + /// + public static string ConnectionUsername { + get { + return ResourceManager.GetString("ConnectionUsername", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Virtual host:. + /// + public static string ConnectionVirtualHost { + get { + return ResourceManager.GetString("ConnectionVirtualHost", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Connection parameters. + /// + public static string ConnectionWindowTitle { + get { + return ResourceManager.GetString("ConnectionWindowTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Petting Zoo - a RabbitMQ Message Viewer. + /// + public static string MainWindowTitle { + get { + return ResourceManager.GetString("MainWindowTitle", resourceCulture); + } + } } } diff --git a/Properties/Resources.resx b/Properties/Resources.resx index af7dbeb..adb4605 100644 --- a/Properties/Resources.resx +++ b/Properties/Resources.resx @@ -46,7 +46,7 @@ mimetype: application/x-microsoft.net.object.binary.base64 value : The object must be serialized with - : System.Serialization.Formatters.Binary.BinaryFormatter + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter : and then encoded with base64 encoding. mimetype: application/x-microsoft.net.object.soap.base64 @@ -60,6 +60,7 @@ : and then encoded with base64 encoding. --> + @@ -68,9 +69,10 @@ - + + @@ -85,9 +87,10 @@ - + + @@ -109,9 +112,42 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Cancel + + + OK + + + Exchange: + + + Host: + + + Password: + + + Port: + + + Routing key: + + + Username: + + + Virtual host: + + + Connection parameters + + + Petting Zoo - a RabbitMQ Message Viewer + \ No newline at end of file diff --git a/Style.xaml b/Style.xaml new file mode 100644 index 0000000..0d55ae3 --- /dev/null +++ b/Style.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/View/ConnectionWindow.xaml b/View/ConnectionWindow.xaml index 6944ad8..3db6fa0 100644 --- a/View/ConnectionWindow.xaml +++ b/View/ConnectionWindow.xaml @@ -1,8 +1,61 @@  - + xmlns:res="clr-namespace:PettingZoo.Properties" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:viewModel="clr-namespace:PettingZoo.ViewModel" + xmlns:infrastructure="clr-namespace:PettingZoo.Infrastructure" + mc:Ignorable="d" + d:DataContext="{d:DesignInstance viewModel:ConnectionViewModel}" + Width="500" + SizeToContent="Height" + ResizeMode="NoResize" + WindowStyle="ToolWindow" + Style="{StaticResource WindowStyle}" + Title="{x:Static res:Resources.ConnectionWindowTitle}" + FocusManager.FocusedElement="{Binding ElementName=HostTextBox}"> + + + - - + + + + + + + + + + + diff --git a/View/MainWindow.xaml.cs b/View/MainWindow.xaml.cs index a53ba48..d751cc0 100644 --- a/View/MainWindow.xaml.cs +++ b/View/MainWindow.xaml.cs @@ -1,15 +1,18 @@ -using System.Windows; +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; using PettingZoo.ViewModel; namespace PettingZoo.View { - /// - /// Interaction logic for MainWindow.xaml - /// public partial class MainWindow : Window { - public MainWindow(MainBaseViewModel viewModel) + public MainWindow(MainViewModel viewModel) { + WindowStartupLocation = WindowStartupLocation.CenterScreen; + InitializeComponent(); DataContext = viewModel; } diff --git a/ViewModel/ConnectionWindowViewModel.cs b/ViewModel/ConnectionViewModel.cs similarity index 50% rename from ViewModel/ConnectionWindowViewModel.cs rename to ViewModel/ConnectionViewModel.cs index 5977e2c..116e7b9 100644 --- a/ViewModel/ConnectionWindowViewModel.cs +++ b/ViewModel/ConnectionViewModel.cs @@ -2,8 +2,10 @@ namespace PettingZoo.ViewModel { - public class ConnectionWindowViewModel + public class ConnectionViewModel { public ConnectionInfo ConnectionInfo { get; set; } + public string Exchange { get; set; } + public string RoutingKey { get; set; } } } diff --git a/ViewModel/MainBaseViewModel.cs b/ViewModel/MainBaseViewModel.cs deleted file mode 100644 index db13eb2..0000000 --- a/ViewModel/MainBaseViewModel.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Windows.Input; -using PettingZoo.Infrastructure; -using PettingZoo.Model; - -namespace PettingZoo.ViewModel -{ - public class MainBaseViewModel : BaseViewModel - { - private readonly IConnectionInfoBuilder connectionInfoBuilder; - private readonly IConnectionFactory connectionFactory; - - private ConnectionInfo connectionInfo; - private IConnection connection; - - - public ConnectionInfo ConnectionInfo { - get - { - return connectionInfo; - } - private set - { - if (value != connectionInfo) - { - connectionInfo = value; - RaisePropertyChanged(); - } - } - } - - public ICommand ConnectCommand { get; private set; } - - - public MainBaseViewModel(IConnectionInfoBuilder connectionInfoBuilder, IConnectionFactory connectionFactory) - { - this.connectionInfoBuilder = connectionInfoBuilder; - this.connectionFactory = connectionFactory; - - ConnectCommand = new DelegateCommand(ConnectExecute); - } - - - protected void ConnectExecute() - { - var newInfo = connectionInfoBuilder.Build(); - if (newInfo != null) - { - ConnectionInfo = newInfo; - connection = connectionFactory.CreateConnection(connectionInfo); - } - } - } -} \ No newline at end of file diff --git a/ViewModel/MainViewModel.cs b/ViewModel/MainViewModel.cs new file mode 100644 index 0000000..98a02f8 --- /dev/null +++ b/ViewModel/MainViewModel.cs @@ -0,0 +1,92 @@ +using System.Windows.Input; +using PettingZoo.Infrastructure; +using PettingZoo.Model; + +namespace PettingZoo.ViewModel +{ + public class MainViewModel : BaseViewModel + { + private readonly IConnectionInfoBuilder connectionInfoBuilder; + private readonly IConnectionFactory connectionFactory; + + private ConnectionInfo connectionInfo; + private IConnection connection; + + private readonly DelegateCommand connectCommand; + private readonly DelegateCommand disconnectCommand; + private readonly DelegateCommand clearCommand; + + + public ConnectionInfo ConnectionInfo { + get + { + return connectionInfo; + } + private set + { + if (value != connectionInfo) + { + connectionInfo = value; + RaisePropertyChanged(); + } + } + } + + public ICommand ConnectCommand { get { return connectCommand; } } + public ICommand DisconnectCommand { get { return disconnectCommand; } } + public ICommand ClearCommand { get { return clearCommand; } } + + + public MainViewModel(IConnectionInfoBuilder connectionInfoBuilder, IConnectionFactory connectionFactory) + { + this.connectionInfoBuilder = connectionInfoBuilder; + this.connectionFactory = connectionFactory; + + connectCommand = new DelegateCommand(ConnectExecute); + disconnectCommand = new DelegateCommand(DisconnectExecute, DisconnectCanExecute); + clearCommand = new DelegateCommand(ClearExecute, ClearCanExecute); + } + + + private void ConnectExecute() + { + var newInfo = connectionInfoBuilder.Build(); + if (newInfo == null) + return; + + ConnectionInfo = newInfo; + connection = connectionFactory.CreateConnection(connectionInfo); + + disconnectCommand.RaiseCanExecuteChanged(); + } + + + private void DisconnectExecute() + { + if (connection != null) + { + connection.Dispose(); + connection = null; + } + + ConnectionInfo = null; + } + + + private bool DisconnectCanExecute() + { + return connection != null; + } + + + private void ClearExecute() + { + } + + + private bool ClearCanExecute() + { + return false; + } + } +} \ No newline at end of file