From 96a52a3705aff495b255707ab9af2cde2a5291a3 Mon Sep 17 00:00:00 2001 From: Ogoun Date: Fri, 3 Jan 2020 00:30:34 +0300 Subject: [PATCH] Fixing and extending --- ZeroLevel.UnitTests/DumpTests.cs | 1 + ZeroLevel.WPF/BaseViewModel.cs | 30 ++ .../Behaviours/ListBoxAutoScrollBehavior.cs | 115 +++++++ ZeroLevel.WPF/Controls/AlignableWrapPanel.cs | 128 ++++++++ ZeroLevel.WPF/Controls/ControlUnlocker.cs | 27 ++ ZeroLevel.WPF/Controls/VirtualToggleButton.cs | 267 ++++++++++++++++ ZeroLevel.WPF/Convertors/BaseConvertor.cs | 33 ++ .../Convertors/BoolToVisibilityConverter.cs | 34 +++ .../Convertors/BooleanAndConverter.cs | 43 +++ ZeroLevel.WPF/Helpers/BitmapSourceHelper.cs | 65 ++++ ZeroLevel.WPF/RelayCommand.cs | 33 ++ ZeroLevel.WPF/ZeroLevel.WPF.csproj | 24 ++ ZeroLevel.sln | 16 +- .../Services/Extensions/TaskExtension.cs | 35 +++ .../Services/Reflection/StackTraceReader.cs | 36 +++ .../Serialization/MessageSerializer.cs | 2 + .../Services/Windows/WindowsLibraryLoader.cs | 286 ++++++++++++++++++ ZeroLevel/ZeroLevel.csproj | 8 +- 18 files changed, 1178 insertions(+), 5 deletions(-) create mode 100644 ZeroLevel.WPF/BaseViewModel.cs create mode 100644 ZeroLevel.WPF/Behaviours/ListBoxAutoScrollBehavior.cs create mode 100644 ZeroLevel.WPF/Controls/AlignableWrapPanel.cs create mode 100644 ZeroLevel.WPF/Controls/ControlUnlocker.cs create mode 100644 ZeroLevel.WPF/Controls/VirtualToggleButton.cs create mode 100644 ZeroLevel.WPF/Convertors/BaseConvertor.cs create mode 100644 ZeroLevel.WPF/Convertors/BoolToVisibilityConverter.cs create mode 100644 ZeroLevel.WPF/Convertors/BooleanAndConverter.cs create mode 100644 ZeroLevel.WPF/Helpers/BitmapSourceHelper.cs create mode 100644 ZeroLevel.WPF/RelayCommand.cs create mode 100644 ZeroLevel.WPF/ZeroLevel.WPF.csproj create mode 100644 ZeroLevel/Services/Extensions/TaskExtension.cs create mode 100644 ZeroLevel/Services/Reflection/StackTraceReader.cs create mode 100644 ZeroLevel/Services/Windows/WindowsLibraryLoader.cs diff --git a/ZeroLevel.UnitTests/DumpTests.cs b/ZeroLevel.UnitTests/DumpTests.cs index 2c7758f..8f4716b 100644 --- a/ZeroLevel.UnitTests/DumpTests.cs +++ b/ZeroLevel.UnitTests/DumpTests.cs @@ -6,6 +6,7 @@ using ZeroLevel.UnitTests.Models; namespace ZeroLevel.UnitTests { + // In developing, not working! public class DumpTests { [Fact] diff --git a/ZeroLevel.WPF/BaseViewModel.cs b/ZeroLevel.WPF/BaseViewModel.cs new file mode 100644 index 0000000..9ebb3ab --- /dev/null +++ b/ZeroLevel.WPF/BaseViewModel.cs @@ -0,0 +1,30 @@ +using System; +using System.ComponentModel; + +namespace ZeroLevel.WPF +{ + public abstract class BaseViewModel + : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged(string propertyName) + { + var handler = this.PropertyChanged; + if (null != handler) + { + this.VerifyPropertyName(propertyName); + var e = new PropertyChangedEventArgs(propertyName); + handler(this, e); + } + } + + public void VerifyPropertyName(string propertyName) + { + if (TypeDescriptor.GetProperties(this)[propertyName] == null) + { + throw new ArgumentException("Invalid property name", propertyName); + } + } + } +} diff --git a/ZeroLevel.WPF/Behaviours/ListBoxAutoScrollBehavior.cs b/ZeroLevel.WPF/Behaviours/ListBoxAutoScrollBehavior.cs new file mode 100644 index 0000000..4fc819b --- /dev/null +++ b/ZeroLevel.WPF/Behaviours/ListBoxAutoScrollBehavior.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; + +namespace ZeroLevel.WPF +{ + public class ListBoxAutoScrollBehavior + { + static readonly Dictionary Associations = + new Dictionary(); + + public static bool GetScrollOnNewItem(DependencyObject obj) + { + return (bool)obj.GetValue(ScrollOnNewItemProperty); + } + + public static void SetScrollOnNewItem(DependencyObject obj, bool value) + { + obj.SetValue(ScrollOnNewItemProperty, value); + } + + public static readonly DependencyProperty ScrollOnNewItemProperty = + DependencyProperty.RegisterAttached( + "ScrollOnNewItem", + typeof(bool), + typeof(ListBoxAutoScrollBehavior), + new UIPropertyMetadata(false, OnScrollOnNewItemChanged)); + + public static void OnScrollOnNewItemChanged( + DependencyObject d, + DependencyPropertyChangedEventArgs e) + { + var listBox = d as ListBox; + if (listBox == null) return; + bool oldValue = (bool)e.OldValue, newValue = (bool)e.NewValue; + if (newValue == oldValue) return; + if (newValue) + { + listBox.Loaded += ListBox_Loaded; + listBox.Unloaded += ListBox_Unloaded; + var itemsSourcePropertyDescriptor = TypeDescriptor.GetProperties(listBox)["ItemsSource"]; + itemsSourcePropertyDescriptor.AddValueChanged(listBox, ListBox_ItemsSourceChanged); + } + else + { + listBox.Loaded -= ListBox_Loaded; + listBox.Unloaded -= ListBox_Unloaded; + if (Associations.ContainsKey(listBox)) + Associations[listBox].Dispose(); + var itemsSourcePropertyDescriptor = TypeDescriptor.GetProperties(listBox)["ItemsSource"]; + itemsSourcePropertyDescriptor.RemoveValueChanged(listBox, ListBox_ItemsSourceChanged); + } + } + + private static void ListBox_ItemsSourceChanged(object sender, EventArgs e) + { + var listBox = (ListBox)sender; + if (Associations.ContainsKey(listBox)) + Associations[listBox].Dispose(); + Associations[listBox] = new Capture(listBox); + } + + private static void ListBox_Unloaded(object sender, RoutedEventArgs e) + { + var listBox = (ListBox)sender; + if (Associations.ContainsKey(listBox)) + Associations[listBox].Dispose(); + listBox.Unloaded -= ListBox_Unloaded; + } + + private static void ListBox_Loaded(object sender, RoutedEventArgs e) + { + var listBox = (ListBox)sender; + var incc = listBox.Items as INotifyCollectionChanged; + if (incc == null) return; + listBox.Loaded -= ListBox_Loaded; + Associations[listBox] = new Capture(listBox); + } + + private class Capture + : IDisposable + { + private readonly ListBox listBox; + private readonly INotifyCollectionChanged incc; + + public Capture(ListBox listBox) + { + this.listBox = listBox; + incc = listBox.ItemsSource as INotifyCollectionChanged; + if (incc != null) + { + incc.CollectionChanged += incc_CollectionChanged; + } + } + + private void incc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + listBox.ScrollIntoView(e.NewItems[0]); + listBox.SelectedItem = e.NewItems[0]; + } + } + + public void Dispose() + { + if (incc != null) + incc.CollectionChanged -= incc_CollectionChanged; + } + } + } +} diff --git a/ZeroLevel.WPF/Controls/AlignableWrapPanel.cs b/ZeroLevel.WPF/Controls/AlignableWrapPanel.cs new file mode 100644 index 0000000..be1ba97 --- /dev/null +++ b/ZeroLevel.WPF/Controls/AlignableWrapPanel.cs @@ -0,0 +1,128 @@ +/* + * + Sample + * + + + + + + + + + + + + + */ + +using System; +using System.Windows; +using System.Windows.Controls; + +namespace ZeroLevel.WPF +{ + /// + /// Позволяет делать горизонтальное выравнивание элементов + /// + public class AlignableWrapPanel : Panel + { + public HorizontalAlignment HorizontalContentAlignment + { + get { return (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty); } + set { SetValue(HorizontalContentAlignmentProperty, value); } + } + + public static readonly DependencyProperty HorizontalContentAlignmentProperty = + DependencyProperty.Register("HorizontalContentAlignment", typeof(HorizontalAlignment), typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange)); + + protected override Size MeasureOverride(Size constraint) + { + Size curLineSize = new Size(); + Size panelSize = new Size(); + UIElementCollection children = base.InternalChildren; + for (int i = 0; i < children.Count; i++) + { + UIElement child = children[i] as UIElement; + // Flow passes its own constraint to children + child.Measure(constraint); + Size sz = child.DesiredSize; + if (curLineSize.Width + sz.Width > constraint.Width) //need to switch to another line + { + panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width); + panelSize.Height += curLineSize.Height; + curLineSize = sz; + if (sz.Width > constraint.Width) // if the element is wider then the constraint - give it a separate line + { + panelSize.Width = Math.Max(sz.Width, panelSize.Width); + panelSize.Height += sz.Height; + curLineSize = new Size(); + } + } + else //continue to accumulate a line + { + curLineSize.Width += sz.Width; + curLineSize.Height = Math.Max(sz.Height, curLineSize.Height); + } + } + // the last line size, if any need to be added + panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width); + panelSize.Height += curLineSize.Height; + return panelSize; + } + + protected override Size ArrangeOverride(Size arrangeBounds) + { + int firstInLine = 0; + Size curLineSize = new Size(); + double accumulatedHeight = 0; + UIElementCollection children = this.InternalChildren; + for (int i = 0; i < children.Count; i++) + { + Size sz = children[i].DesiredSize; + if (curLineSize.Width + sz.Width > arrangeBounds.Width) //need to switch to another line + { + ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, i); + accumulatedHeight += curLineSize.Height; + curLineSize = sz; + if (sz.Width > arrangeBounds.Width) //the element is wider then the constraint - give it a separate line + { + ArrangeLine(accumulatedHeight, sz, arrangeBounds.Width, i, ++i); + accumulatedHeight += sz.Height; + curLineSize = new Size(); + } + firstInLine = i; + } + else //continue to accumulate a line + { + curLineSize.Width += sz.Width; + curLineSize.Height = Math.Max(sz.Height, curLineSize.Height); + } + } + if (firstInLine < children.Count) + ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, children.Count); + return arrangeBounds; + } + + private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end) + { + double x = 0; + if (this.HorizontalContentAlignment == HorizontalAlignment.Center) + { + x = (boundsWidth - lineSize.Width) / 2; + } + else if (this.HorizontalContentAlignment == HorizontalAlignment.Right) + { + x = (boundsWidth - lineSize.Width); + } + UIElementCollection children = InternalChildren; + for (int i = start; i < end; i++) + { + UIElement child = children[i]; + child.Arrange(new Rect(x, y, child.DesiredSize.Width, lineSize.Height)); + x += child.DesiredSize.Width; + } + } + } +} diff --git a/ZeroLevel.WPF/Controls/ControlUnlocker.cs b/ZeroLevel.WPF/Controls/ControlUnlocker.cs new file mode 100644 index 0000000..438cf29 --- /dev/null +++ b/ZeroLevel.WPF/Controls/ControlUnlocker.cs @@ -0,0 +1,27 @@ +using System.Windows.Controls; + +namespace ZeroLevel.WPF.Controls +{ + public static class ControlUnlocker + { + public static void UnlockControl(Control control) + { + if (control != null) + { + if (control.Parent != null) + { + if (control.Parent is Panel) + { + var parent = (System.Windows.Controls.Panel)control.Parent; + parent.Children.Remove(control); + } + else if (control.Parent is ContentControl) + { + var parent = (ContentControl)control.Parent; + parent.Content = null; + } + } + } + } + } +} diff --git a/ZeroLevel.WPF/Controls/VirtualToggleButton.cs b/ZeroLevel.WPF/Controls/VirtualToggleButton.cs new file mode 100644 index 0000000..61d26d5 --- /dev/null +++ b/ZeroLevel.WPF/Controls/VirtualToggleButton.cs @@ -0,0 +1,267 @@ +using System; +using System.Windows; +using System.Windows.Controls.Primitives; +using System.Windows.Input; + +namespace ZeroLevel.WPF +{ + public static class VirtualToggleButton + { + #region attached properties + + #region IsChecked + + /// + /// IsChecked Attached Dependency Property + /// + public static readonly DependencyProperty IsCheckedProperty = + DependencyProperty.RegisterAttached("IsChecked", typeof(Nullable), typeof(VirtualToggleButton), + new FrameworkPropertyMetadata((Nullable)false, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, + new PropertyChangedCallback(OnIsCheckedChanged))); + + /// + /// Gets the IsChecked property. This dependency property + /// indicates whether the toggle button is checked. + /// + public static Nullable GetIsChecked(DependencyObject d) + { + return (Nullable)d.GetValue(IsCheckedProperty); + } + + /// + /// Sets the IsChecked property. This dependency property + /// indicates whether the toggle button is checked. + /// + public static void SetIsChecked(DependencyObject d, Nullable value) + { + d.SetValue(IsCheckedProperty, value); + } + + /// + /// Handles changes to the IsChecked property. + /// + private static void OnIsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + UIElement pseudobutton = d as UIElement; + if (pseudobutton != null) + { + Nullable newValue = (Nullable)e.NewValue; + if (newValue == true) + { + RaiseCheckedEvent(pseudobutton); + } + else if (newValue == false) + { + RaiseUncheckedEvent(pseudobutton); + } + else + { + RaiseIndeterminateEvent(pseudobutton); + } + } + } + + #endregion + + #region IsThreeState + + /// + /// IsThreeState Attached Dependency Property + /// + public static readonly DependencyProperty IsThreeStateProperty = + DependencyProperty.RegisterAttached("IsThreeState", typeof(bool), typeof(VirtualToggleButton), + new FrameworkPropertyMetadata((bool)false)); + + /// + /// Gets the IsThreeState property. This dependency property + /// indicates whether the control supports two or three states. + /// IsChecked can be set to null as a third state when IsThreeState is true. + /// + public static bool GetIsThreeState(DependencyObject d) + { + return (bool)d.GetValue(IsThreeStateProperty); + } + + /// + /// Sets the IsThreeState property. This dependency property + /// indicates whether the control supports two or three states. + /// IsChecked can be set to null as a third state when IsThreeState is true. + /// + public static void SetIsThreeState(DependencyObject d, bool value) + { + d.SetValue(IsThreeStateProperty, value); + } + + #endregion + + #region IsVirtualToggleButton + + /// + /// IsVirtualToggleButton Attached Dependency Property + /// + public static readonly DependencyProperty IsVirtualToggleButtonProperty = + DependencyProperty.RegisterAttached("IsVirtualToggleButton", typeof(bool), typeof(VirtualToggleButton), + new FrameworkPropertyMetadata((bool)false, + new PropertyChangedCallback(OnIsVirtualToggleButtonChanged))); + + /// + /// Gets the IsVirtualToggleButton property. This dependency property + /// indicates whether the object to which the property is attached is treated as a VirtualToggleButton. + /// If true, the object will respond to keyboard and mouse input the same way a ToggleButton would. + /// + public static bool GetIsVirtualToggleButton(DependencyObject d) + { + return (bool)d.GetValue(IsVirtualToggleButtonProperty); + } + + /// + /// Sets the IsVirtualToggleButton property. This dependency property + /// indicates whether the object to which the property is attached is treated as a VirtualToggleButton. + /// If true, the object will respond to keyboard and mouse input the same way a ToggleButton would. + /// + public static void SetIsVirtualToggleButton(DependencyObject d, bool value) + { + d.SetValue(IsVirtualToggleButtonProperty, value); + } + + /// + /// Handles changes to the IsVirtualToggleButton property. + /// + private static void OnIsVirtualToggleButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + IInputElement element = d as IInputElement; + if (element != null) + { + if ((bool)e.NewValue) + { + element.MouseLeftButtonDown += OnMouseLeftButtonDown; + element.KeyDown += OnKeyDown; + } + else + { + element.MouseLeftButtonDown -= OnMouseLeftButtonDown; + element.KeyDown -= OnKeyDown; + } + } + } + + #endregion + + #endregion + + #region routed events + + #region Checked + + /// + /// A static helper method to raise the Checked event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseCheckedEvent(UIElement target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(); + args.RoutedEvent = ToggleButton.CheckedEvent; + RaiseEvent(target, args); + return args; + } + + #endregion + + #region Unchecked + + /// + /// A static helper method to raise the Unchecked event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseUncheckedEvent(UIElement target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(); + args.RoutedEvent = ToggleButton.UncheckedEvent; + RaiseEvent(target, args); + return args; + } + + #endregion + + #region Indeterminate + + /// + /// A static helper method to raise the Indeterminate event on a target element. + /// + /// UIElement or ContentElement on which to raise the event + internal static RoutedEventArgs RaiseIndeterminateEvent(UIElement target) + { + if (target == null) return null; + + RoutedEventArgs args = new RoutedEventArgs(); + args.RoutedEvent = ToggleButton.IndeterminateEvent; + RaiseEvent(target, args); + return args; + } + + #endregion + + #endregion + + #region private methods + + private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + e.Handled = true; + UpdateIsChecked(sender as DependencyObject); + } + + private static void OnKeyDown(object sender, KeyEventArgs e) + { + if (e.OriginalSource == sender) + { + if (e.Key == Key.Space) + { + // ignore alt+space which invokes the system menu + if ((Keyboard.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt) return; + + UpdateIsChecked(sender as DependencyObject); + e.Handled = true; + + } + else if (e.Key == Key.Enter && (bool)(sender as DependencyObject).GetValue(KeyboardNavigation.AcceptsReturnProperty)) + { + UpdateIsChecked(sender as DependencyObject); + e.Handled = true; + } + } + } + + private static void UpdateIsChecked(DependencyObject d) + { + Nullable isChecked = GetIsChecked(d); + if (isChecked == true) + { + SetIsChecked(d, GetIsThreeState(d) ? (Nullable)null : (Nullable)false); + } + else + { + SetIsChecked(d, isChecked.HasValue); + } + } + + private static void RaiseEvent(DependencyObject target, RoutedEventArgs args) + { + if (target is UIElement) + { + (target as UIElement).RaiseEvent(args); + } + else if (target is ContentElement) + { + (target as ContentElement).RaiseEvent(args); + } + } + + #endregion + } +} diff --git a/ZeroLevel.WPF/Convertors/BaseConvertor.cs b/ZeroLevel.WPF/Convertors/BaseConvertor.cs new file mode 100644 index 0000000..25464ec --- /dev/null +++ b/ZeroLevel.WPF/Convertors/BaseConvertor.cs @@ -0,0 +1,33 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Markup; + +namespace ZeroLevel.WPF +{ + public abstract class BaseConvertor + : MarkupExtension, IValueConverter + where T : class, new() + { + /// + /// Must be implemented in inheritor. + /// + public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture); + + /// + /// Override if needed. + /// + public virtual object ConvertBack(object value, Type targetType, object parameter, + CultureInfo culture) + { + throw new NotImplementedException(); + } + + #region MarkupExtension members + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } + #endregion + } +} diff --git a/ZeroLevel.WPF/Convertors/BoolToVisibilityConverter.cs b/ZeroLevel.WPF/Convertors/BoolToVisibilityConverter.cs new file mode 100644 index 0000000..135aed8 --- /dev/null +++ b/ZeroLevel.WPF/Convertors/BoolToVisibilityConverter.cs @@ -0,0 +1,34 @@ +using System; +using System.Globalization; +using System.Windows; + +namespace ZeroLevel.WPF +{ + public sealed class BoolToVisibilityConverter : BaseConvertor + { + public Visibility TrueValue { get; set; } + public Visibility FalseValue { get; set; } + + public BoolToVisibilityConverter() + { + TrueValue = Visibility.Visible; + FalseValue = Visibility.Collapsed; + } + + public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (!(value is bool)) + return null; + return (bool)value ? TrueValue : FalseValue; + } + + public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (Equals(value, TrueValue)) + return true; + if (Equals(value, FalseValue)) + return false; + return null; + } + } +} diff --git a/ZeroLevel.WPF/Convertors/BooleanAndConverter.cs b/ZeroLevel.WPF/Convertors/BooleanAndConverter.cs new file mode 100644 index 0000000..5e9f97b --- /dev/null +++ b/ZeroLevel.WPF/Convertors/BooleanAndConverter.cs @@ -0,0 +1,43 @@ +using System; +using System.Windows.Data; + +namespace ZeroLevel.WPF +{ + public class BooleanAndConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + foreach (object value in values) + { + if ((value is bool) && (bool)value == false) + { + return false; + } + } + return true; + } + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException("BooleanAndConverter is a OneWay converter."); + } + } + + public class BooleanOrConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + foreach (object value in values) + { + if ((value is bool) && (bool)value == true) + { + return true; + } + } + return false; + } + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException("BooleanAndConverter is a OneWay converter."); + } + } +} diff --git a/ZeroLevel.WPF/Helpers/BitmapSourceHelper.cs b/ZeroLevel.WPF/Helpers/BitmapSourceHelper.cs new file mode 100644 index 0000000..76219e1 --- /dev/null +++ b/ZeroLevel.WPF/Helpers/BitmapSourceHelper.cs @@ -0,0 +1,65 @@ +using System.Drawing; +using System.Drawing.Imaging; +using System.Windows.Media.Imaging; + +namespace ZeroLevel.WPF +{ + public static class BitmapSourceHelper + { + public static BitmapSource LoadBitmap(Bitmap bmp) + { + if (ImageFormat.Jpeg.Equals(bmp.RawFormat)) + { + return LoadBitmap(bmp, ImageFormat.Jpeg); + } + else if (ImageFormat.Png.Equals(bmp.RawFormat)) + { + return LoadBitmap(bmp, ImageFormat.Png); + } + else if (ImageFormat.Gif.Equals(bmp.RawFormat)) + { + return LoadBitmap(bmp, ImageFormat.Gif); + } + else if (ImageFormat.Bmp.Equals(bmp.RawFormat) || ImageFormat.MemoryBmp.Equals(bmp.RawFormat)) + { + return LoadBitmap(bmp, ImageFormat.Bmp); + } + else if (ImageFormat.Emf.Equals(bmp.RawFormat)) + { + return LoadBitmap(bmp, ImageFormat.Emf); + } + else if (ImageFormat.Exif.Equals(bmp.RawFormat)) + { + return LoadBitmap(bmp, ImageFormat.Exif); + } + else if (ImageFormat.Icon.Equals(bmp.RawFormat)) + { + return LoadBitmap(bmp, ImageFormat.Icon); + } + else if (ImageFormat.Tiff.Equals(bmp.RawFormat)) + { + return LoadBitmap(bmp, ImageFormat.Tiff); + } + else if (ImageFormat.Wmf.Equals(bmp.RawFormat)) + { + return LoadBitmap(bmp, ImageFormat.Wmf); + } + return LoadBitmap(bmp, ImageFormat.Bmp); + } + + public static BitmapSource LoadBitmap(Bitmap bmp, ImageFormat format) + { + var bi = new BitmapImage(); + using (var ms = new System.IO.MemoryStream()) + { + bi.BeginInit(); + bi.StreamSource = ms; + bi.CacheOption = BitmapCacheOption.OnLoad; + bmp.Save(ms, format); + bi.EndInit(); + bi.Freeze(); + } + return bi; + } + } +} diff --git a/ZeroLevel.WPF/RelayCommand.cs b/ZeroLevel.WPF/RelayCommand.cs new file mode 100644 index 0000000..3103420 --- /dev/null +++ b/ZeroLevel.WPF/RelayCommand.cs @@ -0,0 +1,33 @@ +using System; + +namespace ZeroLevel.WPF +{ + public class RelayCommand + : System.Windows.Input.ICommand + { + private readonly Predicate _canExecute; + private readonly Action _execute; + + public RelayCommand(Predicate canExecute, Action execute) + { + _canExecute = canExecute; + _execute = execute; + } + + public event EventHandler CanExecuteChanged + { + add => System.Windows.Input.CommandManager.RequerySuggested += value; + remove => System.Windows.Input.CommandManager.RequerySuggested -= value; + } + + public bool CanExecute(object parameter) + { + return _canExecute(parameter); + } + + public void Execute(object parameter) + { + _execute(parameter); + } + } +} diff --git a/ZeroLevel.WPF/ZeroLevel.WPF.csproj b/ZeroLevel.WPF/ZeroLevel.WPF.csproj new file mode 100644 index 0000000..ab76156 --- /dev/null +++ b/ZeroLevel.WPF/ZeroLevel.WPF.csproj @@ -0,0 +1,24 @@ + + + + netstandard2.1 + + + + + + + + + + ..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\PresentationCore.dll + + + ..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\PresentationFramework.dll + + + ..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.Xaml.dll + + + + diff --git a/ZeroLevel.sln b/ZeroLevel.sln index b2a8f56..a6b9837 100644 --- a/ZeroLevel.sln +++ b/ZeroLevel.sln @@ -35,7 +35,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationTests", "Confi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjectionTests", "DependencyInjectionTests\DependencyInjectionTests.csproj", "{665B38E3-A5F2-4AD0-946B-209D80C1AA40}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZeroLevel.Logger", "ZeroLevel.Logger\ZeroLevel.Logger.csproj", "{D1C061DB-3565-43C3-B8F3-628DE4908750}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZeroLevel.Logger", "ZeroLevel.Logger\ZeroLevel.Logger.csproj", "{D1C061DB-3565-43C3-B8F3-628DE4908750}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZeroLevel.WPF", "ZeroLevel.WPF\ZeroLevel.WPF.csproj", "{0D70D688-1E21-4E9D-AA49-4D255DF27D8D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -227,6 +229,18 @@ Global {D1C061DB-3565-43C3-B8F3-628DE4908750}.Release|x64.Build.0 = Release|Any CPU {D1C061DB-3565-43C3-B8F3-628DE4908750}.Release|x86.ActiveCfg = Release|Any CPU {D1C061DB-3565-43C3-B8F3-628DE4908750}.Release|x86.Build.0 = Release|Any CPU + {0D70D688-1E21-4E9D-AA49-4D255DF27D8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D70D688-1E21-4E9D-AA49-4D255DF27D8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D70D688-1E21-4E9D-AA49-4D255DF27D8D}.Debug|x64.ActiveCfg = Debug|Any CPU + {0D70D688-1E21-4E9D-AA49-4D255DF27D8D}.Debug|x64.Build.0 = Debug|Any CPU + {0D70D688-1E21-4E9D-AA49-4D255DF27D8D}.Debug|x86.ActiveCfg = Debug|Any CPU + {0D70D688-1E21-4E9D-AA49-4D255DF27D8D}.Debug|x86.Build.0 = Debug|Any CPU + {0D70D688-1E21-4E9D-AA49-4D255DF27D8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D70D688-1E21-4E9D-AA49-4D255DF27D8D}.Release|Any CPU.Build.0 = Release|Any CPU + {0D70D688-1E21-4E9D-AA49-4D255DF27D8D}.Release|x64.ActiveCfg = Release|Any CPU + {0D70D688-1E21-4E9D-AA49-4D255DF27D8D}.Release|x64.Build.0 = Release|Any CPU + {0D70D688-1E21-4E9D-AA49-4D255DF27D8D}.Release|x86.ActiveCfg = Release|Any CPU + {0D70D688-1E21-4E9D-AA49-4D255DF27D8D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ZeroLevel/Services/Extensions/TaskExtension.cs b/ZeroLevel/Services/Extensions/TaskExtension.cs new file mode 100644 index 0000000..1349f51 --- /dev/null +++ b/ZeroLevel/Services/Extensions/TaskExtension.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading.Tasks; + +namespace ZeroLevel.Services.Extensions +{ + public static class TaskExtension + { + public static T WaitResult(this Task task) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + task.Wait(); + if (task.IsFaulted) + { + if (task.Exception != null) throw task.Exception; + } + return task.Result; + } + + public static void WaitWithoutResult(this Task task) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + task.Wait(); + if (task.IsFaulted) + { + if (task.Exception != null) throw task.Exception; + } + } + } +} diff --git a/ZeroLevel/Services/Reflection/StackTraceReader.cs b/ZeroLevel/Services/Reflection/StackTraceReader.cs new file mode 100644 index 0000000..865c63c --- /dev/null +++ b/ZeroLevel/Services/Reflection/StackTraceReader.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace ZeroLevel.Services.Reflection +{ + /// + /// Performs read of stack trace + /// + public static class StackTraceReader + { + /// + /// Read current stack trace + /// + /// Result - enumerable of tuples as 'Type':'MethodName' + public static Tuple[] ReadFrames() + { + var result = new List>(); + var stackTrace = new StackTrace(); + foreach (var frame in stackTrace.GetFrames() ?? Enumerable.Empty()) + { + var method = frame.GetMethod(); + if (method != null && method.DeclaringType != null) + { + var type = method.DeclaringType.Name; + if (false == type.Equals("StackTraceReader", StringComparison.Ordinal)) + { + result.Add(new Tuple(type, method.Name)); + } + } + } + return result.ToArray(); + } + } +} diff --git a/ZeroLevel/Services/Serialization/MessageSerializer.cs b/ZeroLevel/Services/Serialization/MessageSerializer.cs index 6945eef..e745368 100644 --- a/ZeroLevel/Services/Serialization/MessageSerializer.cs +++ b/ZeroLevel/Services/Serialization/MessageSerializer.cs @@ -121,6 +121,7 @@ namespace ZeroLevel.Services.Serialization public static T DeserializeCompatible(byte[] data) { + if (data == null || data.Length == 0) return default(T); if (typeof(IBinarySerializable).IsAssignableFrom(typeof(T))) { using (var reader = new MemoryStreamReader(data)) @@ -168,6 +169,7 @@ namespace ZeroLevel.Services.Serialization public static T Copy(T value) where T : IBinarySerializable { + if (default == value) return default; using (var writer = new MemoryStreamWriter()) { value.Serialize(writer); diff --git a/ZeroLevel/Services/Windows/WindowsLibraryLoader.cs b/ZeroLevel/Services/Windows/WindowsLibraryLoader.cs new file mode 100644 index 0000000..96b6f51 --- /dev/null +++ b/ZeroLevel/Services/Windows/WindowsLibraryLoader.cs @@ -0,0 +1,286 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; + +namespace ZeroLevel.Services.Windows +{ + public sealed class WindowsLibraryLoader + { + + #region Fields + + private const string ProcessorArchitecture = "PROCESSOR_ARCHITECTURE"; + + private const string DllFileExtension = ".dll"; + + private const string DllDirectory = "dll"; + + private readonly Dictionary _ProcessorArchitectureAddressWidthPlatforms = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {"x86", 4}, + {"AMD64", 8}, + {"IA64", 8}, + {"ARM", 4} + }; + + private readonly Dictionary _ProcessorArchitecturePlatforms = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {"x86", "x86"}, + {"AMD64", "x64"}, + {"IA64", "Itanium"}, + {"ARM", "WinCE"} + }; + + private readonly object _SyncLock = new object(); + + private static readonly IDictionary LoadedLibraries = new Dictionary(); + + [System.Runtime.InteropServices.DllImport("kernel32", EntryPoint = "LoadLibrary", CallingConvention = System.Runtime.InteropServices.CallingConvention.Winapi, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] + private static extern IntPtr Win32LoadLibrary(string dllPath); + + #endregion + + #region Properties + + public static bool IsCurrentPlatformSupported() + { + return RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows); + } + + public static bool IsWindows() + { + return Environment.OSVersion.Platform == PlatformID.Win32NT || + Environment.OSVersion.Platform == PlatformID.Win32S || + Environment.OSVersion.Platform == PlatformID.Win32Windows || + Environment.OSVersion.Platform == PlatformID.WinCE; + } + + #endregion + + #region Methods + + #region Helpers + + private static string FixUpDllFileName(string fileName) + { + if (string.IsNullOrEmpty(fileName)) + return fileName; + + if (!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return fileName; + + if (!fileName.EndsWith(DllFileExtension, StringComparison.OrdinalIgnoreCase)) + return $"{fileName}{DllFileExtension}"; + + return fileName; + } + + private ProcessArchitectureInfo GetProcessArchitecture() + { + // BUG: Will this always be reliable? + var processArchitecture = Environment.GetEnvironmentVariable(ProcessorArchitecture); + var processInfo = new ProcessArchitectureInfo(); + if (!string.IsNullOrEmpty(processArchitecture)) + { + // Sanity check + processInfo.Architecture = processArchitecture; + } + else + { + processInfo.AddWarning("Failed to detect processor architecture, falling back to x86."); + processInfo.Architecture = (IntPtr.Size == 8) ? "x64" : "x86"; + } + + var addressWidth = this._ProcessorArchitectureAddressWidthPlatforms[processInfo.Architecture]; + if (addressWidth != IntPtr.Size) + { + if (String.Equals(processInfo.Architecture, "AMD64", StringComparison.OrdinalIgnoreCase) && IntPtr.Size == 4) + { + // fall back to x86 if detected x64 but has an address width of 32 bits. + processInfo.Architecture = "x86"; + processInfo.AddWarning("Expected the detected processing architecture of {0} to have an address width of {1} Bytes but was {2} Bytes, falling back to x86.", processInfo.Architecture, addressWidth, IntPtr.Size); + } + else + { + // no fallback possible + processInfo.AddWarning("Expected the detected processing architecture of {0} to have an address width of {1} Bytes but was {2} Bytes.", processInfo.Architecture, addressWidth, IntPtr.Size); + + } + } + + return processInfo; + } + + private string GetPlatformName(string processorArchitecture) + { + if (String.IsNullOrEmpty(processorArchitecture)) + return null; + + string platformName; + if (this._ProcessorArchitecturePlatforms.TryGetValue(processorArchitecture, out platformName)) + { + return platformName; + } + + return null; + } + + public void LoadLibraries(IEnumerable dlls) + { + if (!IsWindows()) + return; + + foreach (var dll in dlls) + LoadLibrary(dll); + } + + private void LoadLibrary(string dllName) + { + if (!IsCurrentPlatformSupported()) + return; + + try + { + lock (this._SyncLock) + { + if (LoadedLibraries.ContainsKey(dllName)) + return; + + var processArch = GetProcessArchitecture(); + IntPtr dllHandle; + + // Try loading from executing assembly domain + var executingAssembly = GetType().GetTypeInfo().Assembly; + var baseDirectory = Path.GetDirectoryName(executingAssembly.Location); + dllHandle = LoadLibraryInternal(dllName, baseDirectory, processArch); + if (dllHandle != IntPtr.Zero) return; + + // Gets the pathname of the base directory that the assembly resolver uses to probe for assemblies. + // https://github.com/dotnet/corefx/issues/2221 + baseDirectory = AppContext.BaseDirectory; + dllHandle = LoadLibraryInternal(dllName, baseDirectory, processArch); + if (dllHandle != IntPtr.Zero) return; + + // Finally try the working directory + baseDirectory = Path.GetFullPath(Directory.GetCurrentDirectory()); + dllHandle = LoadLibraryInternal(dllName, baseDirectory, processArch); + if (dllHandle != IntPtr.Zero) return; + + var errorMessage = new StringBuilder(); + errorMessage.Append($"Failed to find dll \"{dllName}\", for processor architecture {processArch.Architecture}."); + if (processArch.HasWarnings) + { + // include process detection warnings + errorMessage.Append($"\r\nWarnings: \r\n{processArch.WarningText()}"); + } + + throw new Exception(errorMessage.ToString()); + } + } + catch (Exception e) + { + System.Diagnostics.Debug.WriteLine(e.Message); + } + } + + private IntPtr LoadLibraryInternal(string dllName, string baseDirectory, ProcessArchitectureInfo processArchInfo) + { + var platformName = GetPlatformName(processArchInfo.Architecture); + var expectedDllDirectory = Path.Combine( + Path.Combine(baseDirectory, DllDirectory), platformName); + return this.LoadLibraryRaw(dllName, expectedDllDirectory); + } + + private IntPtr LoadLibraryRaw(string dllName, string baseDirectory) + { + var libraryHandle = IntPtr.Zero; + var fileName = FixUpDllFileName(Path.Combine(baseDirectory, dllName)); + + // Show where we're trying to load the file from + Debug.WriteLine($"Trying to load native library \"{fileName}\"..."); + + if (File.Exists(fileName)) + { + // Attempt to load dll + try + { + libraryHandle = Win32LoadLibrary(fileName); + if (libraryHandle != IntPtr.Zero) + { + // library has been loaded + Debug.WriteLine($"Successfully loaded native library \"{fileName}\"."); + LoadedLibraries.Add(dllName, libraryHandle); + } + else + { + Debug.WriteLine($"Failed to load native library \"{fileName}\".\r\nCheck windows event log."); + } + } + catch (Exception e) + { + var lastError = Marshal.GetLastWin32Error(); + Debug.WriteLine($"Failed to load native library \"{fileName}\".\r\nLast Error:{lastError}\r\nCheck inner exception and\\or windows event log.\r\nInner Exception: {e}"); + } + } + else + { + Debug.WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture, "The native library \"{0}\" does not exist.", fileName)); + } + + return libraryHandle; + } + + #endregion + + #endregion + + private class ProcessArchitectureInfo + { + + #region Constructors + + public ProcessArchitectureInfo() + { + this.Warnings = new List(); + } + + #endregion + + #region Properties + + public string Architecture + { + get; set; + } + + private List Warnings + { + get; + } + + #endregion + + #region Methods + + public void AddWarning(string format, params object[] args) + { + Warnings.Add(String.Format(format, args)); + } + + public bool HasWarnings => Warnings.Count > 0; + + public string WarningText() + { + return string.Join("\r\n", Warnings.ToArray()); + } + + #endregion + + } + + } +} diff --git a/ZeroLevel/ZeroLevel.csproj b/ZeroLevel/ZeroLevel.csproj index 27bcd21..89bf9c4 100644 --- a/ZeroLevel/ZeroLevel.csproj +++ b/ZeroLevel/ZeroLevel.csproj @@ -6,16 +6,16 @@ ogoun ogoun - 3.1.7.0 - Fixes + 3.1.8.0 + Fixing and extending https://github.com/ogoun/Zero/wiki Copyright Ogoun 2019 https://raw.githubusercontent.com/ogoun/Zero/master/zero.png https://github.com/ogoun/Zero GitHub - 3.1.7 - 3.1.7.0 + 3.1.8 + 3.1.8.0 AnyCPU;x64