From 13b1f70a17a598e4756938ce1554e6433c5a56f7 Mon Sep 17 00:00:00 2001 From: dhq_boiler Date: Sat, 2 Oct 2021 13:37:49 +0900 Subject: [PATCH 1/7] bugfix --- boilersGraphics/ViewModels/MainWindowViewModel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/boilersGraphics/ViewModels/MainWindowViewModel.cs b/boilersGraphics/ViewModels/MainWindowViewModel.cs index 16b00a6e..e0f4bcf9 100644 --- a/boilersGraphics/ViewModels/MainWindowViewModel.cs +++ b/boilersGraphics/ViewModels/MainWindowViewModel.cs @@ -87,7 +87,10 @@ public MainWindowViewModel(IDialogService dialogService) DiagramViewModel.EdgeColors.ToAddOperation(exchange.New.Value).ExecuteTo(Recorder.Current); foreach (var item in DiagramViewModel.SelectedItems.Value.OfType()) { - Recorder.Current.ExecuteSetProperty(item, "EdgeColor.Value", exchange.New.Value); + if (item is SnapPointViewModel snapPoint) + Recorder.Current.ExecuteSetProperty(snapPoint.Parent.Value, "EdgeColor.Value", exchange.New.Value); + else + Recorder.Current.ExecuteSetProperty(item, "EdgeColor.Value", exchange.New.Value); } Recorder.EndRecode(); } From a9a631344b6cd11dc95c9224b46ee492bb761738 Mon Sep 17 00:00:00 2001 From: dhq_boiler Date: Sat, 2 Oct 2021 13:37:57 +0900 Subject: [PATCH 2/7] bugfix --- boilersGraphics/ViewModels/MainWindowViewModel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/boilersGraphics/ViewModels/MainWindowViewModel.cs b/boilersGraphics/ViewModels/MainWindowViewModel.cs index e0f4bcf9..ddb3f9e3 100644 --- a/boilersGraphics/ViewModels/MainWindowViewModel.cs +++ b/boilersGraphics/ViewModels/MainWindowViewModel.cs @@ -155,7 +155,10 @@ public MainWindowViewModel(IDialogService dialogService) Recorder.BeginRecode(); foreach (var item in DiagramViewModel.SelectedItems.Value?.OfType()) { - Recorder.Current.ExecuteSetProperty(item, "EdgeThickness.Value", x.Value); + if (item is SnapPointViewModel snapPoint) + Recorder.Current.ExecuteSetProperty(snapPoint.Parent.Value, "EdgeThickness.Value", x.Value); + else + Recorder.Current.ExecuteSetProperty(item, "EdgeThickness.Value", x.Value); } Recorder.EndRecode(); } From 0ecc62b4d048b1b2eb250c4b2241401a0d506c55 Mon Sep 17 00:00:00 2001 From: dhq_boiler Date: Sat, 2 Oct 2021 16:19:33 +0900 Subject: [PATCH 3/7] add Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject and etc. --- .../Helper.cs | 55 ++ .../PrivateObject.cs | 915 ++++++++++++++++++ .../PrivateType.cs | 520 ++++++++++ .../RuntimeTypeHelper.cs | 509 ++++++++++ .../boilersGraphics.Test.csproj | 4 + 5 files changed, 2003 insertions(+) create mode 100644 boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/Helper.cs create mode 100644 boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/PrivateObject.cs create mode 100644 boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/PrivateType.cs create mode 100644 boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/RuntimeTypeHelper.cs diff --git a/boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/Helper.cs b/boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/Helper.cs new file mode 100644 index 00000000..568da3f6 --- /dev/null +++ b/boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/Helper.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestTools.UnitTesting +{ + using System; + + /// + /// The helper. + /// + internal static class Helper + { + /// + /// The check parameter not null. + /// + /// + /// The parameter. + /// + /// + /// The parameter name. + /// + /// + /// The message. + /// + /// Throws argument null exception when parameter is null. + internal static void CheckParameterNotNull(object param, string parameterName, string message) + { + if (param == null) + { + throw new ArgumentNullException(parameterName, message); + } + } + + /// + /// The check parameter not null or empty. + /// + /// + /// The parameter. + /// + /// + /// The parameter name. + /// + /// + /// The message. + /// + /// Throws ArgumentException when parameter is null. + internal static void CheckParameterNotNullOrEmpty(string param, string parameterName, string message) + { + if (string.IsNullOrEmpty(param)) + { + throw new ArgumentException(message, parameterName); + } + } + } +} diff --git a/boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/PrivateObject.cs b/boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/PrivateObject.cs new file mode 100644 index 00000000..641ca03a --- /dev/null +++ b/boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/PrivateObject.cs @@ -0,0 +1,915 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestTools.UnitTesting +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Reflection; + + /// + /// This class represents the live NON public INTERNAL object in the system + /// + public class PrivateObject + { + #region Data + + // bind everything + private const BindingFlags BindToEveryThing = BindingFlags.Default | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public; + + private static BindingFlags constructorFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance | BindingFlags.NonPublic; + + private object target; // automatically initialized to null + private Type originalType; // automatically initialized to null + + private Dictionary> methodCache; // automatically initialized to null + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class that contains + /// the already existing object of the private class + /// + /// object that serves as starting point to reach the private members + /// the de-referencing string using . that points to the object to be retrieved as in m_X.m_Y.m_Z + [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "We don't know anything about the object other than that it's an object, so 'obj' seems reasonable")] + public PrivateObject(object obj, string memberToAccess) + { + Helper.CheckParameterNotNull(obj, "obj", string.Empty); + ValidateAccessString(memberToAccess); + + PrivateObject temp = obj as PrivateObject; + if (temp == null) + { + temp = new PrivateObject(obj); + } + + // Split The access string + string[] arr = memberToAccess.Split(new char[] { '.' }); + + for (int i = 0; i < arr.Length; i++) + { + object next = temp.InvokeHelper(arr[i], BindToEveryThing | BindingFlags.Instance | BindingFlags.GetField | BindingFlags.GetProperty, null, CultureInfo.InvariantCulture); + temp = new PrivateObject(next); + } + + this.target = temp.target; + this.originalType = temp.originalType; + } + + /// + /// Initializes a new instance of the class that wraps the + /// specified type. + /// + /// Name of the assembly + /// fully qualified name + /// Arguments to pass to the constructor + public PrivateObject(string assemblyName, string typeName, params object[] args) + : this(assemblyName, typeName, null, args) + { + } + + /// + /// Initializes a new instance of the class that wraps the + /// specified type. + /// + /// Name of the assembly + /// fully qualified name + /// An array of objects representing the number, order, and type of the parameters for the constructor to get + /// Arguments to pass to the constructor + public PrivateObject(string assemblyName, string typeName, Type[] parameterTypes, object[] args) + : this(Type.GetType(string.Format(CultureInfo.InvariantCulture, "{0}, {1}", typeName, assemblyName), false), parameterTypes, args) + { + Helper.CheckParameterNotNull(assemblyName, "assemblyName", string.Empty); + Helper.CheckParameterNotNull(typeName, "typeName", string.Empty); + } + + /// + /// Initializes a new instance of the class that wraps the + /// specified type. + /// + /// type of the object to create + /// Arguments to pass to the constructor + public PrivateObject(Type type, params object[] args) + : this(type, null, args) + { + Helper.CheckParameterNotNull(type, "type", string.Empty); + } + + /// + /// Initializes a new instance of the class that wraps the + /// specified type. + /// + /// type of the object to create + /// An array of objects representing the number, order, and type of the parameters for the constructor to get + /// Arguments to pass to the constructor + public PrivateObject(Type type, Type[] parameterTypes, object[] args) + { + Helper.CheckParameterNotNull(type, "type", string.Empty); + object o; + if (parameterTypes != null) + { + ConstructorInfo ci = type.GetConstructor(BindToEveryThing, null, parameterTypes, null); + if (ci == null) + { + throw new ArgumentException("FrameworkMessages.PrivateAccessorConstructorNotFound"); + } + + try + { + o = ci.Invoke(args); + } + catch (TargetInvocationException e) + { + Debug.Assert(e.InnerException != null, "Inner exception should not be null."); + if (e.InnerException != null) + { + throw e.InnerException; + } + + throw; + } + } + else + { + o = Activator.CreateInstance(type, constructorFlags, null, args, null); + } + + this.ConstructFrom(o); + } + + /// + /// Initializes a new instance of the class that wraps + /// the given object. + /// + /// object to wrap + [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "We don't know anything about the object other than that it's an object, so 'obj' seems reasonable")] + public PrivateObject(object obj) + { + Helper.CheckParameterNotNull(obj, "obj", string.Empty); + this.ConstructFrom(obj); + } + + /// + /// Initializes a new instance of the class that wraps + /// the given object. + /// + /// object to wrap + /// PrivateType object + [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "We don't know anything about the object other than that it's an object, so 'obj' seems reasonable")] + public PrivateObject(object obj, PrivateType type) + { + Helper.CheckParameterNotNull(type, "type", string.Empty); + this.target = obj; + this.originalType = type.ReferencedType; + } + + #endregion + + /// + /// Gets or sets the target + /// + public object Target + { + get + { + return this.target; + } + + set + { + Helper.CheckParameterNotNull(value, "Target", string.Empty); + this.target = value; + this.originalType = value.GetType(); + } + } + + /// + /// Gets the type of underlying object + /// + public Type RealType + { + get + { + return this.originalType; + } + } + + private Dictionary> GenericMethodCache + { + get + { + if (this.methodCache == null) + { + this.BuildGenericMethodCacheForType(this.originalType); + } + + Debug.Assert(this.methodCache != null, "Invalid method cache for type."); + + return this.methodCache; + } + } + + /// + /// returns the hash code of the target object + /// + /// int representing hashcode of the target object + public override int GetHashCode() + { + Debug.Assert(this.target != null, "target should not be null."); + return this.target.GetHashCode(); + } + + /// + /// Equals + /// + /// Object with whom to compare + /// returns true if the objects are equal. + public override bool Equals(object obj) + { + if (this != obj) + { + Debug.Assert(this.target != null, "target should not be null."); + if (typeof(PrivateObject) == obj?.GetType()) + { + return this.target.Equals(((PrivateObject)obj).target); + } + else + { + return false; + } + } + + return true; + } + + /// + /// Invokes the specified method + /// + /// Name of the method + /// Arguments to pass to the member to invoke. + /// Result of method call + public object Invoke(string name, params object[] args) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + return this.Invoke(name, null, args, CultureInfo.InvariantCulture); + } + + /// + /// Invokes the specified method + /// + /// Name of the method + /// An array of objects representing the number, order, and type of the parameters for the method to get. + /// Arguments to pass to the member to invoke. + /// Result of method call + public object Invoke(string name, Type[] parameterTypes, object[] args) + { + return this.Invoke(name, parameterTypes, args, CultureInfo.InvariantCulture); + } + + /// + /// Invokes the specified method + /// + /// Name of the method + /// An array of objects representing the number, order, and type of the parameters for the method to get. + /// Arguments to pass to the member to invoke. + /// An array of types corresponding to the types of the generic arguments. + /// Result of method call + public object Invoke(string name, Type[] parameterTypes, object[] args, Type[] typeArguments) + { + return this.Invoke(name, BindToEveryThing, parameterTypes, args, CultureInfo.InvariantCulture, typeArguments); + } + + /// + /// Invokes the specified method + /// + /// Name of the method + /// Arguments to pass to the member to invoke. + /// Culture info + /// Result of method call + public object Invoke(string name, object[] args, CultureInfo culture) + { + return this.Invoke(name, null, args, culture); + } + + /// + /// Invokes the specified method + /// + /// Name of the method + /// An array of objects representing the number, order, and type of the parameters for the method to get. + /// Arguments to pass to the member to invoke. + /// Culture info + /// Result of method call + public object Invoke(string name, Type[] parameterTypes, object[] args, CultureInfo culture) + { + return this.Invoke(name, BindToEveryThing, parameterTypes, args, culture); + } + + /// + /// Invokes the specified method + /// + /// Name of the method + /// A bitmask comprised of one or more that specify how the search is conducted. + /// Arguments to pass to the member to invoke. + /// Result of method call + public object Invoke(string name, BindingFlags bindingFlags, params object[] args) + { + return this.Invoke(name, bindingFlags, null, args, CultureInfo.InvariantCulture); + } + + /// + /// Invokes the specified method + /// + /// Name of the method + /// A bitmask comprised of one or more that specify how the search is conducted. + /// An array of objects representing the number, order, and type of the parameters for the method to get. + /// Arguments to pass to the member to invoke. + /// Result of method call + public object Invoke(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args) + { + return this.Invoke(name, bindingFlags, parameterTypes, args, CultureInfo.InvariantCulture); + } + + /// + /// Invokes the specified method + /// + /// Name of the method + /// A bitmask comprised of one or more that specify how the search is conducted. + /// Arguments to pass to the member to invoke. + /// Culture info + /// Result of method call + public object Invoke(string name, BindingFlags bindingFlags, object[] args, CultureInfo culture) + { + return this.Invoke(name, bindingFlags, null, args, culture); + } + + /// + /// Invokes the specified method + /// + /// Name of the method + /// A bitmask comprised of one or more that specify how the search is conducted. + /// An array of objects representing the number, order, and type of the parameters for the method to get. + /// Arguments to pass to the member to invoke. + /// Culture info + /// Result of method call + public object Invoke(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args, CultureInfo culture) + { + return this.Invoke(name, bindingFlags, parameterTypes, args, culture, null); + } + + /// + /// Invokes the specified method + /// + /// Name of the method + /// A bitmask comprised of one or more that specify how the search is conducted. + /// An array of objects representing the number, order, and type of the parameters for the method to get. + /// Arguments to pass to the member to invoke. + /// Culture info + /// An array of types corresponding to the types of the generic arguments. + /// Result of method call + public object Invoke(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args, CultureInfo culture, Type[] typeArguments) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + if (parameterTypes != null) + { + bindingFlags |= BindToEveryThing | BindingFlags.Instance; + + // Fix up the parameter types + MethodInfo member = this.originalType.GetMethod(name, bindingFlags, null, parameterTypes, null); + + // If the method was not found and type arguments were provided for generic parameters, + // attempt to look up a generic method. + if ((member == null) && (typeArguments != null)) + { + // This method may contain generic parameters...if so, the previous call to + // GetMethod() will fail because it doesn't fully support generic parameters. + + // Look in the method cache to see if there is a generic method + // on the incoming type that contains the correct signature. + member = this.GetGenericMethodFromCache(name, parameterTypes, typeArguments, bindingFlags, null); + } + + if (member == null) + { + throw new ArgumentException( + string.Format(CultureInfo.CurrentCulture, "FrameworkMessages.PrivateAccessorMemberNotFound", name)); + } + + try + { + if (member.IsGenericMethodDefinition) + { + MethodInfo constructed = member.MakeGenericMethod(typeArguments); + return constructed.Invoke(this.target, bindingFlags, null, args, culture); + } + else + { + return member.Invoke(this.target, bindingFlags, null, args, culture); + } + } + catch (TargetInvocationException e) + { + Debug.Assert(e.InnerException != null, "Inner exception should not be null."); + if (e.InnerException != null) + { + throw e.InnerException; + } + + throw; + } + } + else + { + return this.InvokeHelper(name, bindingFlags | BindingFlags.InvokeMethod, args, culture); + } + } + + /// + /// Gets the array element using array of subscripts for each dimension + /// + /// Name of the member + /// the indices of array + /// An array of elements. + public object GetArrayElement(string name, params int[] indices) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + return this.GetArrayElement(name, BindToEveryThing, indices); + } + + /// + /// Sets the array element using array of subscripts for each dimension + /// + /// Name of the member + /// Value to set + /// the indices of array + public void SetArrayElement(string name, object value, params int[] indices) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + this.SetArrayElement(name, BindToEveryThing, value, indices); + } + + /// + /// Gets the array element using array of subscripts for each dimension + /// + /// Name of the member + /// A bitmask comprised of one or more that specify how the search is conducted. + /// the indices of array + /// An array of elements. + public object GetArrayElement(string name, BindingFlags bindingFlags, params int[] indices) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + Array arr = (Array)this.InvokeHelper(name, BindingFlags.GetField | bindingFlags, null, CultureInfo.InvariantCulture); + return arr.GetValue(indices); + } + + /// + /// Sets the array element using array of subscripts for each dimension + /// + /// Name of the member + /// A bitmask comprised of one or more that specify how the search is conducted. + /// Value to set + /// the indices of array + public void SetArrayElement(string name, BindingFlags bindingFlags, object value, params int[] indices) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + Array arr = (Array)this.InvokeHelper(name, BindingFlags.GetField | bindingFlags, null, CultureInfo.InvariantCulture); + arr.SetValue(value, indices); + } + + /// + /// Get the field + /// + /// Name of the field + /// The field. + public object GetField(string name) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + return this.GetField(name, BindToEveryThing); + } + + /// + /// Sets the field + /// + /// Name of the field + /// value to set + public void SetField(string name, object value) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + this.SetField(name, BindToEveryThing, value); + } + + /// + /// Gets the field + /// + /// Name of the field + /// A bitmask comprised of one or more that specify how the search is conducted. + /// The field. + public object GetField(string name, BindingFlags bindingFlags) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + return this.InvokeHelper(name, BindingFlags.GetField | bindingFlags, null, CultureInfo.InvariantCulture); + } + + /// + /// Sets the field + /// + /// Name of the field + /// A bitmask comprised of one or more that specify how the search is conducted. + /// value to set + public void SetField(string name, BindingFlags bindingFlags, object value) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + this.InvokeHelper(name, BindingFlags.SetField | bindingFlags, new object[] { value }, CultureInfo.InvariantCulture); + } + + /// + /// Get the field or property + /// + /// Name of the field or property + /// The field or property. + public object GetFieldOrProperty(string name) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + return this.GetFieldOrProperty(name, BindToEveryThing); + } + + /// + /// Sets the field or property + /// + /// Name of the field or property + /// value to set + public void SetFieldOrProperty(string name, object value) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + this.SetFieldOrProperty(name, BindToEveryThing, value); + } + + /// + /// Gets the field or property + /// + /// Name of the field or property + /// A bitmask comprised of one or more that specify how the search is conducted. + /// The field or property. + public object GetFieldOrProperty(string name, BindingFlags bindingFlags) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + return this.InvokeHelper(name, BindingFlags.GetField | BindingFlags.GetProperty | bindingFlags, null, CultureInfo.InvariantCulture); + } + + /// + /// Sets the field or property + /// + /// Name of the field or property + /// A bitmask comprised of one or more that specify how the search is conducted. + /// value to set + public void SetFieldOrProperty(string name, BindingFlags bindingFlags, object value) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + this.InvokeHelper(name, BindingFlags.SetField | BindingFlags.SetProperty | bindingFlags, new object[] { value }, CultureInfo.InvariantCulture); + } + + /// + /// Gets the property + /// + /// Name of the property + /// Arguments to pass to the member to invoke. + /// The property. + public object GetProperty(string name, params object[] args) + { + return this.GetProperty(name, null, args); + } + + /// + /// Gets the property + /// + /// Name of the property + /// An array of objects representing the number, order, and type of the parameters for the indexed property. + /// Arguments to pass to the member to invoke. + /// The property. + public object GetProperty(string name, Type[] parameterTypes, object[] args) + { + return this.GetProperty(name, BindToEveryThing, parameterTypes, args); + } + + /// + /// Set the property + /// + /// Name of the property + /// value to set + /// Arguments to pass to the member to invoke. + public void SetProperty(string name, object value, params object[] args) + { + this.SetProperty(name, null, value, args); + } + + /// + /// Set the property + /// + /// Name of the property + /// An array of objects representing the number, order, and type of the parameters for the indexed property. + /// value to set + /// Arguments to pass to the member to invoke. + public void SetProperty(string name, Type[] parameterTypes, object value, object[] args) + { + this.SetProperty(name, BindToEveryThing, value, parameterTypes, args); + } + + /// + /// Gets the property + /// + /// Name of the property + /// A bitmask comprised of one or more that specify how the search is conducted. + /// Arguments to pass to the member to invoke. + /// The property. + public object GetProperty(string name, BindingFlags bindingFlags, params object[] args) + { + return this.GetProperty(name, bindingFlags, null, args); + } + + /// + /// Gets the property + /// + /// Name of the property + /// A bitmask comprised of one or more that specify how the search is conducted. + /// An array of objects representing the number, order, and type of the parameters for the indexed property. + /// Arguments to pass to the member to invoke. + /// The property. + public object GetProperty(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + if (parameterTypes != null) + { + PropertyInfo pi = this.originalType.GetProperty(name, bindingFlags, null, null, parameterTypes, null); + if (pi == null) + { + throw new ArgumentException( + string.Format(CultureInfo.CurrentCulture, "FrameworkMessages.PrivateAccessorMemberNotFound", name)); + } + + return pi.GetValue(this.target, args); + } + else + { + return this.InvokeHelper(name, bindingFlags | BindingFlags.GetProperty, args, null); + } + } + + /// + /// Sets the property + /// + /// Name of the property + /// A bitmask comprised of one or more that specify how the search is conducted. + /// value to set + /// Arguments to pass to the member to invoke. + public void SetProperty(string name, BindingFlags bindingFlags, object value, params object[] args) + { + this.SetProperty(name, bindingFlags, value, null, args); + } + + /// + /// Sets the property + /// + /// Name of the property + /// A bitmask comprised of one or more that specify how the search is conducted. + /// value to set + /// An array of objects representing the number, order, and type of the parameters for the indexed property. + /// Arguments to pass to the member to invoke. + public void SetProperty(string name, BindingFlags bindingFlags, object value, Type[] parameterTypes, object[] args) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + + if (parameterTypes != null) + { + PropertyInfo pi = this.originalType.GetProperty(name, bindingFlags, null, null, parameterTypes, null); + if (pi == null) + { + throw new ArgumentException( + string.Format(CultureInfo.CurrentCulture, "FrameworkMessages.PrivateAccessorMemberNotFound", name)); + } + + pi.SetValue(this.target, value, args); + } + else + { + object[] pass = new object[(args?.Length ?? 0) + 1]; + pass[0] = value; + args?.CopyTo(pass, 1); + this.InvokeHelper(name, bindingFlags | BindingFlags.SetProperty, pass, null); + } + } + + #region Private Helpers + + /// + /// Validate access string + /// + /// access string + private static void ValidateAccessString(string access) + { + Helper.CheckParameterNotNull(access, "access", string.Empty); + if (access.Length == 0) + { + throw new ArgumentException("FrameworkMessages.AccessStringInvalidSyntax"); + } + + string[] arr = access.Split('.'); + foreach (string str in arr) + { + if ((str.Length == 0) || (str.IndexOfAny(new char[] { ' ', '\t', '\n' }) != -1)) + { + throw new ArgumentException("FrameworkMessages.AccessStringInvalidSyntax"); + } + } + } + + /// + /// Invokes the member + /// + /// Name of the member + /// Additional attributes + /// Arguments for the invocation + /// Culture + /// Result of the invocation + private object InvokeHelper(string name, BindingFlags bindingFlags, object[] args, CultureInfo culture) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + Debug.Assert(this.target != null, "Internal Error: Null reference is returned for internal object"); + + // Invoke the actual Method + try + { + return this.originalType.InvokeMember(name, bindingFlags, null, this.target, args, culture); + } + catch (TargetInvocationException e) + { + Debug.Assert(e.InnerException != null, "Inner exception should not be null."); + if (e.InnerException != null) + { + throw e.InnerException; + } + + throw; + } + } + + private void ConstructFrom(object obj) + { + Helper.CheckParameterNotNull(obj, "obj", string.Empty); + this.target = obj; + this.originalType = obj.GetType(); + } + + private void BuildGenericMethodCacheForType(Type t) + { + Debug.Assert(t != null, "type should not be null."); + this.methodCache = new Dictionary>(); + + MethodInfo[] members = t.GetMethods(BindToEveryThing); + + foreach (MethodInfo member in members) + { + if (member.IsGenericMethod || member.IsGenericMethodDefinition) + { + // automatically initialized to null + if (!this.GenericMethodCache.TryGetValue(member.Name, out LinkedList listByName)) + { + listByName = new LinkedList(); + this.GenericMethodCache.Add(member.Name, listByName); + } + + Debug.Assert(listByName != null, "list should not be null."); + listByName.AddLast(member); + } + } + } + + /// + /// Extracts the most appropriate generic method signature from the current private type. + /// + /// The name of the method in which to search the signature cache. + /// An array of types corresponding to the types of the parameters in which to search. + /// An array of types corresponding to the types of the generic arguments. + /// to further filter the method signatures. + /// Modifiers for parameters. + /// A method info instance. + private MethodInfo GetGenericMethodFromCache(string methodName, Type[] parameterTypes, Type[] typeArguments, BindingFlags bindingFlags, ParameterModifier[] modifiers) + { + Debug.Assert(!string.IsNullOrEmpty(methodName), "Invalid method name."); + Debug.Assert(parameterTypes != null, "Invalid parameter type array."); + Debug.Assert(typeArguments != null, "Invalid type arguments array."); + + // Build a preliminary list of method candidates that contain roughly the same signature. + var methodCandidates = this.GetMethodCandidates(methodName, parameterTypes, typeArguments, bindingFlags, modifiers); + + // Search of ambiguous methods (methods with the same signature). + MethodInfo[] finalCandidates = new MethodInfo[methodCandidates.Count]; + methodCandidates.CopyTo(finalCandidates, 0); + + if ((parameterTypes != null) && (parameterTypes.Length == 0)) + { + for (int i = 0; i < finalCandidates.Length; i++) + { + MethodInfo methodInfo = finalCandidates[i]; + + if (!RuntimeTypeHelper.CompareMethodSigAndName(methodInfo, finalCandidates[0])) + { + throw new AmbiguousMatchException(); + } + } + + // All the methods have the exact same name and sig so return the most derived one. + return RuntimeTypeHelper.FindMostDerivedNewSlotMeth(finalCandidates, finalCandidates.Length) as MethodInfo; + } + + // Now that we have a preliminary list of candidates, select the most appropriate one. + return RuntimeTypeHelper.SelectMethod(bindingFlags, finalCandidates, parameterTypes, modifiers) as MethodInfo; + } + + private LinkedList GetMethodCandidates(string methodName, Type[] parameterTypes, Type[] typeArguments, BindingFlags bindingFlags, ParameterModifier[] modifiers) + { + Debug.Assert(!string.IsNullOrEmpty(methodName), "methodName should not be null."); + Debug.Assert(parameterTypes != null, "parameterTypes should not be null."); + Debug.Assert(typeArguments != null, "typeArguments should not be null."); + + LinkedList methodCandidates = new LinkedList(); + + if (!this.GenericMethodCache.TryGetValue(methodName, out var methods)) + { + return methodCandidates; + } + + Debug.Assert(methods != null, "methods should not be null."); + + foreach (MethodInfo candidate in methods) + { + bool paramMatch = true; + ParameterInfo[] candidateParams = null; + Type[] genericArgs = candidate.GetGenericArguments(); + Type sourceParameterType = null; + + if (genericArgs.Length != typeArguments.Length) + { + continue; + } + + // Since we can't just get the correct MethodInfo from Reflection, + // we will just match the number of parameters, their order, and their type + var methodCandidate = candidate; + candidateParams = methodCandidate.GetParameters(); + + if (candidateParams.Length != parameterTypes.Length) + { + continue; + } + + // Exact binding + if ((bindingFlags & BindingFlags.ExactBinding) != 0) + { + int i = 0; + + foreach (ParameterInfo candidateParam in candidateParams) + { + sourceParameterType = parameterTypes[i++]; + + if (candidateParam.ParameterType.ContainsGenericParameters) + { + // Since we have a generic parameter here, just make sure the IsArray matches. + if (candidateParam.ParameterType.IsArray != sourceParameterType.IsArray) + { + paramMatch = false; + break; + } + } + else + { + if (candidateParam.ParameterType != sourceParameterType) + { + paramMatch = false; + break; + } + } + } + + if (paramMatch) + { + methodCandidates.AddLast(methodCandidate); + continue; + } + } + else + { + methodCandidates.AddLast(methodCandidate); + } + } + + return methodCandidates; + } + + #endregion + } +} diff --git a/boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/PrivateType.cs b/boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/PrivateType.cs new file mode 100644 index 00000000..665c35f6 --- /dev/null +++ b/boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/PrivateType.cs @@ -0,0 +1,520 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestTools.UnitTesting +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.Reflection; + + /// + /// This class represents a private class for the Private Accessors functionality. + /// + public class PrivateType + { + /// + /// Binds to everything + /// + private const BindingFlags BindToEveryThing = BindingFlags.Default + | BindingFlags.NonPublic | BindingFlags.Instance + | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy; + + /// + /// The wrapped type. + /// + private Type type; + + /// + /// Initializes a new instance of the class that contains the private type. + /// + /// Assembly name + /// fully qualified name of the + public PrivateType(string assemblyName, string typeName) + { + Helper.CheckParameterNotNullOrEmpty(assemblyName, "assemblyName", string.Empty); + Helper.CheckParameterNotNullOrEmpty(typeName, "typeName", string.Empty); + Assembly asm = Assembly.Load(assemblyName); + + this.type = asm.GetType(typeName, true); + } + + /// + /// Initializes a new instance of the class that contains + /// the private type from the type object + /// + /// The wrapped Type to create. + public PrivateType(Type type) + { + this.type = type ?? throw new ArgumentNullException(nameof(type)); + } + + /// + /// Gets the referenced type + /// + public Type ReferencedType => this.type; + + /// + /// Invokes static member + /// + /// Name of the member to InvokeHelper + /// Arguments to the invocation + /// Result of invocation + public object InvokeStatic(string name, params object[] args) + { + return this.InvokeStatic(name, null, args, CultureInfo.InvariantCulture); + } + + /// + /// Invokes static member + /// + /// Name of the member to InvokeHelper + /// An array of objects representing the number, order, and type of the parameters for the method to invoke + /// Arguments to the invocation + /// Result of invocation + public object InvokeStatic(string name, Type[] parameterTypes, object[] args) + { + return this.InvokeStatic(name, parameterTypes, args, CultureInfo.InvariantCulture); + } + + /// + /// Invokes static member + /// + /// Name of the member to InvokeHelper + /// An array of objects representing the number, order, and type of the parameters for the method to invoke + /// Arguments to the invocation + /// An array of types corresponding to the types of the generic arguments. + /// Result of invocation + public object InvokeStatic(string name, Type[] parameterTypes, object[] args, Type[] typeArguments) + { + return this.InvokeStatic(name, BindToEveryThing, parameterTypes, args, CultureInfo.InvariantCulture, typeArguments); + } + + /// + /// Invokes the static method + /// + /// Name of the member + /// Arguments to the invocation + /// Culture + /// Result of invocation + public object InvokeStatic(string name, object[] args, CultureInfo culture) + { + return this.InvokeStatic(name, null, args, culture); + } + + /// + /// Invokes the static method + /// + /// Name of the member + /// An array of objects representing the number, order, and type of the parameters for the method to invoke + /// Arguments to the invocation + /// Culture info + /// Result of invocation + public object InvokeStatic(string name, Type[] parameterTypes, object[] args, CultureInfo culture) + { + return this.InvokeStatic(name, BindingFlags.InvokeMethod, parameterTypes, args, culture); + } + + /// + /// Invokes the static method + /// + /// Name of the member + /// Additional invocation attributes + /// Arguments to the invocation + /// Result of invocation + public object InvokeStatic(string name, BindingFlags bindingFlags, params object[] args) + { + return this.InvokeStatic(name, bindingFlags, null, args, CultureInfo.InvariantCulture); + } + + /// + /// Invokes the static method + /// + /// Name of the member + /// Additional invocation attributes + /// An array of objects representing the number, order, and type of the parameters for the method to invoke + /// Arguments to the invocation + /// Result of invocation + public object InvokeStatic(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args) + { + return this.InvokeStatic(name, bindingFlags, parameterTypes, args, CultureInfo.InvariantCulture); + } + + /// + /// Invokes the static method + /// + /// Name of the member + /// Additional invocation attributes + /// Arguments to the invocation + /// Culture + /// Result of invocation + public object InvokeStatic(string name, BindingFlags bindingFlags, object[] args, CultureInfo culture) + { + return this.InvokeStatic(name, bindingFlags, null, args, culture); + } + + /// + /// Invokes the static method + /// + /// Name of the member + /// Additional invocation attributes + /// /// An array of objects representing the number, order, and type of the parameters for the method to invoke + /// Arguments to the invocation + /// Culture + /// Result of invocation + public object InvokeStatic(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args, CultureInfo culture) + { + return this.InvokeStatic(name, bindingFlags, parameterTypes, args, culture, null); + } + + /// + /// Invokes the static method + /// + /// Name of the member + /// Additional invocation attributes + /// /// An array of objects representing the number, order, and type of the parameters for the method to invoke + /// Arguments to the invocation + /// Culture + /// An array of types corresponding to the types of the generic arguments. + /// Result of invocation + public object InvokeStatic(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args, CultureInfo culture, Type[] typeArguments) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + if (parameterTypes != null) + { + MethodInfo member = this.type.GetMethod(name, bindingFlags | BindToEveryThing | BindingFlags.Static, null, parameterTypes, null); + if (member == null) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "FrameworkMessages.PrivateAccessorMemberNotFound", name)); + } + + try + { + if (member.IsGenericMethodDefinition) + { + MethodInfo constructed = member.MakeGenericMethod(typeArguments); + return constructed.Invoke(null, bindingFlags, null, args, culture); + } + else + { + return member.Invoke(null, bindingFlags, null, args, culture); + } + } + catch (TargetInvocationException e) + { + Debug.Assert(e.InnerException != null, "Inner Exception should not be null."); + if (e.InnerException != null) + { + throw e.InnerException; + } + + throw; + } + } + else + { + return this.InvokeHelperStatic(name, bindingFlags | BindingFlags.InvokeMethod, args, culture); + } + } + + /// + /// Gets the element in static array + /// + /// Name of the array + /// + /// A one-dimensional array of 32-bit integers that represent the indexes specifying + /// the position of the element to get. For instance, to access a[10][11] the indices would be {10,11} + /// + /// element at the specified location + public object GetStaticArrayElement(string name, params int[] indices) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + return this.GetStaticArrayElement(name, BindToEveryThing, indices); + } + + /// + /// Sets the member of the static array + /// + /// Name of the array + /// value to set + /// + /// A one-dimensional array of 32-bit integers that represent the indexes specifying + /// the position of the element to set. For instance, to access a[10][11] the array would be {10,11} + /// + public void SetStaticArrayElement(string name, object value, params int[] indices) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + this.SetStaticArrayElement(name, BindToEveryThing, value, indices); + } + + /// + /// Gets the element in static array + /// + /// Name of the array + /// Additional InvokeHelper attributes + /// + /// A one-dimensional array of 32-bit integers that represent the indexes specifying + /// the position of the element to get. For instance, to access a[10][11] the array would be {10,11} + /// + /// element at the specified location + public object GetStaticArrayElement(string name, BindingFlags bindingFlags, params int[] indices) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + Array arr = (Array)this.InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.GetProperty | bindingFlags, null, CultureInfo.InvariantCulture); + return arr.GetValue(indices); + } + + /// + /// Sets the member of the static array + /// + /// Name of the array + /// Additional InvokeHelper attributes + /// value to set + /// + /// A one-dimensional array of 32-bit integers that represent the indexes specifying + /// the position of the element to set. For instance, to access a[10][11] the array would be {10,11} + /// + public void SetStaticArrayElement(string name, BindingFlags bindingFlags, object value, params int[] indices) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + Array arr = (Array)this.InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Static | bindingFlags, null, CultureInfo.InvariantCulture); + arr.SetValue(value, indices); + } + + /// + /// Gets the static field + /// + /// Name of the field + /// The static field. + public object GetStaticField(string name) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + return this.GetStaticField(name, BindToEveryThing); + } + + /// + /// Sets the static field + /// + /// Name of the field + /// Argument to the invocation + public void SetStaticField(string name, object value) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + this.SetStaticField(name, BindToEveryThing, value); + } + + /// + /// Gets the static field using specified InvokeHelper attributes + /// + /// Name of the field + /// Additional invocation attributes + /// The static field. + public object GetStaticField(string name, BindingFlags bindingFlags) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + return this.InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.Static | bindingFlags, null, CultureInfo.InvariantCulture); + } + + /// + /// Sets the static field using binding attributes + /// + /// Name of the field + /// Additional InvokeHelper attributes + /// Argument to the invocation + public void SetStaticField(string name, BindingFlags bindingFlags, object value) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + this.InvokeHelperStatic(name, BindingFlags.SetField | bindingFlags | BindingFlags.Static, new[] { value }, CultureInfo.InvariantCulture); + } + + /// + /// Gets the static field or property + /// + /// Name of the field or property + /// The static field or property. + public object GetStaticFieldOrProperty(string name) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + return this.GetStaticFieldOrProperty(name, BindToEveryThing); + } + + /// + /// Sets the static field or property + /// + /// Name of the field or property + /// Value to be set to field or property + public void SetStaticFieldOrProperty(string name, object value) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + this.SetStaticFieldOrProperty(name, BindToEveryThing, value); + } + + /// + /// Gets the static field or property using specified InvokeHelper attributes + /// + /// Name of the field or property + /// Additional invocation attributes + /// The static field or property. + public object GetStaticFieldOrProperty(string name, BindingFlags bindingFlags) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + return this.InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Static | bindingFlags, null, CultureInfo.InvariantCulture); + } + + /// + /// Sets the static field or property using binding attributes + /// + /// Name of the field or property + /// Additional invocation attributes + /// Value to be set to field or property + public void SetStaticFieldOrProperty(string name, BindingFlags bindingFlags, object value) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + this.InvokeHelperStatic(name, BindingFlags.SetField | BindingFlags.SetProperty | bindingFlags | BindingFlags.Static, new[] { value }, CultureInfo.InvariantCulture); + } + + /// + /// Gets the static property + /// + /// Name of the field or property + /// Arguments to the invocation + /// The static property. + public object GetStaticProperty(string name, params object[] args) + { + return this.GetStaticProperty(name, BindToEveryThing, args); + } + + /// + /// Sets the static property + /// + /// Name of the property + /// Value to be set to field or property + /// Arguments to pass to the member to invoke. + public void SetStaticProperty(string name, object value, params object[] args) + { + this.SetStaticProperty(name, BindToEveryThing, value, null, args); + } + + /// + /// Sets the static property + /// + /// Name of the property + /// Value to be set to field or property + /// An array of objects representing the number, order, and type of the parameters for the indexed property. + /// Arguments to pass to the member to invoke. + public void SetStaticProperty(string name, object value, Type[] parameterTypes, object[] args) + { + this.SetStaticProperty(name, BindingFlags.SetProperty, value, parameterTypes, args); + } + + /// + /// Gets the static property + /// + /// Name of the property + /// Additional invocation attributes. + /// Arguments to pass to the member to invoke. + /// The static property. + public object GetStaticProperty(string name, BindingFlags bindingFlags, params object[] args) + { + return this.GetStaticProperty(name, BindingFlags.GetProperty | BindingFlags.Static | bindingFlags, null, args); + } + + /// + /// Gets the static property + /// + /// Name of the property + /// Additional invocation attributes. + /// An array of objects representing the number, order, and type of the parameters for the indexed property. + /// Arguments to pass to the member to invoke. + /// The static property. + public object GetStaticProperty(string name, BindingFlags bindingFlags, Type[] parameterTypes, object[] args) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + if (parameterTypes != null) + { + PropertyInfo pi = this.type.GetProperty(name, bindingFlags | BindingFlags.Static, null, null, parameterTypes, null); + if (pi == null) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "FrameworkMessages.PrivateAccessorMemberNotFound", name)); + } + + return pi.GetValue(null, args); + } + else + { + return this.InvokeHelperStatic(name, bindingFlags | BindingFlags.GetProperty, args, null); + } + } + + /// + /// Sets the static property + /// + /// Name of the property + /// Additional invocation attributes. + /// Value to be set to field or property + /// Optional index values for indexed properties. The indexes of indexed properties are zero-based. This value should be null for non-indexed properties. + public void SetStaticProperty(string name, BindingFlags bindingFlags, object value, params object[] args) + { + this.SetStaticProperty(name, bindingFlags, value, null, args); + } + + /// + /// Sets the static property + /// + /// Name of the property + /// Additional invocation attributes. + /// Value to be set to field or property + /// An array of objects representing the number, order, and type of the parameters for the indexed property. + /// Arguments to pass to the member to invoke. + public void SetStaticProperty(string name, BindingFlags bindingFlags, object value, Type[] parameterTypes, object[] args) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + + if (parameterTypes != null) + { + PropertyInfo pi = this.type.GetProperty(name, bindingFlags | BindingFlags.Static, null, null, parameterTypes, null); + if (pi == null) + { + throw new ArgumentException( + string.Format(CultureInfo.CurrentCulture, "FrameworkMessages.PrivateAccessorMemberNotFound", name)); + } + + pi.SetValue(null, value, args); + } + else + { + object[] pass = new object[(args?.Length ?? 0) + 1]; + pass[0] = value; + args?.CopyTo(pass, 1); + this.InvokeHelperStatic(name, bindingFlags | BindingFlags.SetProperty, pass, null); + } + } + + /// + /// Invokes the static method + /// + /// Name of the member + /// Additional invocation attributes + /// Arguments to the invocation + /// Culture + /// Result of invocation + private object InvokeHelperStatic(string name, BindingFlags bindingFlags, object[] args, CultureInfo culture) + { + Helper.CheckParameterNotNull(name, "name", string.Empty); + try + { + return this.type.InvokeMember(name, bindingFlags | BindToEveryThing | BindingFlags.Static, null, null, args, culture); + } + catch (TargetInvocationException e) + { + Debug.Assert(e.InnerException != null, "Inner Exception should not be null."); + if (e.InnerException != null) + { + throw e.InnerException; + } + + throw; + } + } + } +} diff --git a/boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/RuntimeTypeHelper.cs b/boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/RuntimeTypeHelper.cs new file mode 100644 index 00000000..73fbedf6 --- /dev/null +++ b/boilersGraphics.Test/Microsoft.VisualStudio.TestTools.UnitTesting/RuntimeTypeHelper.cs @@ -0,0 +1,509 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestTools.UnitTesting +{ + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Reflection; + + /// + /// Provides method signature discovery for generic methods. + /// + internal class RuntimeTypeHelper + { + /// + /// Compares the method signatures of these two methods. + /// + /// Method1 + /// Method2 + /// True if they are similar. + internal static bool CompareMethodSigAndName(MethodBase m1, MethodBase m2) + { + ParameterInfo[] params1 = m1.GetParameters(); + ParameterInfo[] params2 = m2.GetParameters(); + + if (params1.Length != params2.Length) + { + return false; + } + + int numParams = params1.Length; + for (int i = 0; i < numParams; i++) + { + if (params1[i].ParameterType != params2[i].ParameterType) + { + return false; + } + } + + return true; + } + + /// + /// Gets the hierarchy depth from the base type of the provided type. + /// + /// The type. + /// The depth. + internal static int GetHierarchyDepth(Type t) + { + int depth = 0; + + Type currentType = t; + do + { + depth++; + currentType = currentType.BaseType; + } + while (currentType != null); + + return depth; + } + + /// + /// Finds most derived type with the provided information. + /// + /// Candidate matches. + /// Number of matches. + /// The most derived method. + internal static MethodBase FindMostDerivedNewSlotMeth(MethodBase[] match, int cMatches) + { + int deepestHierarchy = 0; + MethodBase methWithDeepestHierarchy = null; + + for (int i = 0; i < cMatches; i++) + { + // Calculate the depth of the hierarchy of the declaring type of the + // current method. + int currentHierarchyDepth = GetHierarchyDepth(match[i].DeclaringType); + + // Two methods with the same hierarchy depth are not allowed. This would + // mean that there are 2 methods with the same name and sig on a given type + // which is not allowed, unless one of them is vararg... + if (currentHierarchyDepth == deepestHierarchy) + { + if (methWithDeepestHierarchy != null) + { + Debug.Assert( + methWithDeepestHierarchy != null && ((match[i].CallingConvention & CallingConventions.VarArgs) + | (methWithDeepestHierarchy.CallingConvention & CallingConventions.VarArgs)) != 0, + "Calling conventions: " + match[i].CallingConvention + " - " + methWithDeepestHierarchy.CallingConvention); + } + + throw new AmbiguousMatchException(); + } + + // Check to see if this method is on the most derived class. + if (currentHierarchyDepth > deepestHierarchy) + { + deepestHierarchy = currentHierarchyDepth; + methWithDeepestHierarchy = match[i]; + } + } + + return methWithDeepestHierarchy; + } + + /// + /// Given a set of methods that match the base criteria, select a method based + /// upon an array of types. This method should return null if no method matches + /// the criteria. + /// + /// Binding specification. + /// Candidate matches + /// Types + /// Parameter modifiers. + /// Matching method. Null if none matches. + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")] + internal static MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers) + { + if (match == null) + { + throw new ArgumentNullException(nameof(match)); + } + + int i; + int j; + + Type[] realTypes = new Type[types.Length]; + for (i = 0; i < types.Length; i++) + { + realTypes[i] = types[i].UnderlyingSystemType; + } + + types = realTypes; + + // If there are no methods to match to, then return null, indicating that no method + // matches the criteria + if (match.Length == 0) + { + return null; + } + + // Find all the methods that can be described by the types parameter. + // Remove all of them that cannot. + int curIdx = 0; + for (i = 0; i < match.Length; i++) + { + ParameterInfo[] par = match[i].GetParameters(); + if (par.Length != types.Length) + { + continue; + } + + for (j = 0; j < types.Length; j++) + { + Type pCls = par[j].ParameterType; + + if (pCls.ContainsGenericParameters) + { + if (pCls.IsArray != types[j].IsArray) + { + break; + } + } + else + { + if (pCls == types[j]) + { + continue; + } + + if (pCls == typeof(object)) + { + continue; + } + else + { + if (!pCls.IsAssignableFrom(types[j])) + { + break; + } + } + } + } + + if (j == types.Length) + { + match[curIdx++] = match[i]; + } + } + + if (curIdx == 0) + { + return null; + } + + if (curIdx == 1) + { + return match[0]; + } + + // Walk all of the methods looking the most specific method to invoke + int currentMin = 0; + bool ambig = false; + int[] paramOrder = new int[types.Length]; + for (i = 0; i < types.Length; i++) + { + paramOrder[i] = i; + } + + for (i = 1; i < curIdx; i++) + { + int newMin = FindMostSpecificMethod(match[currentMin], paramOrder, null, match[i], paramOrder, null, types, null); + if (newMin == 0) + { + ambig = true; + } + else + { + if (newMin == 2) + { + currentMin = i; + ambig = false; + currentMin = i; + } + } + } + + if (ambig) + { + throw new AmbiguousMatchException(); + } + + return match[currentMin]; + } + + /// + /// Finds the most specific method in the two methods provided. + /// + /// Method 1 + /// Parameter order for Method 1 + /// Parameter array type. + /// Method 2 + /// Parameter order for Method 2 + /// >Parameter array type. + /// Types to search in. + /// Args. + /// An int representing the match. + internal static int FindMostSpecificMethod( + MethodBase m1, + int[] paramOrder1, + Type paramArrayType1, + MethodBase m2, + int[] paramOrder2, + Type paramArrayType2, + Type[] types, + object[] args) + { + // Find the most specific method based on the parameters. + int res = FindMostSpecific( + m1.GetParameters(), + paramOrder1, + paramArrayType1, + m2.GetParameters(), + paramOrder2, + paramArrayType2, + types, + args); + + // If the match was not ambiguous then return the result. + if (res != 0) + { + return res; + } + + // Check to see if the methods have the exact same name and signature. + if (CompareMethodSigAndName(m1, m2)) + { + // Determine the depth of the declaring types for both methods. + int hierarchyDepth1 = GetHierarchyDepth(m1.DeclaringType); + int hierarchyDepth2 = GetHierarchyDepth(m2.DeclaringType); + + // The most derived method is the most specific one. + if (hierarchyDepth1 == hierarchyDepth2) + { + return 0; + } + else if (hierarchyDepth1 < hierarchyDepth2) + { + return 2; + } + else + { + return 1; + } + } + + // The match is ambiguous. + return 0; + } + + /// + /// Finds the most specific method in the two methods provided. + /// + /// Method 1 + /// Parameter order for Method 1 + /// Parameter array type. + /// Method 2 + /// Parameter order for Method 2 + /// >Parameter array type. + /// Types to search in. + /// Args. + /// An int representing the match. + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")] + internal static int FindMostSpecific( + ParameterInfo[] p1, + int[] paramOrder1, + Type paramArrayType1, + ParameterInfo[] p2, + int[] paramOrder2, + Type paramArrayType2, + Type[] types, + object[] args) + { + // A method using params is always less specific than one not using params + if (paramArrayType1 != null && paramArrayType2 == null) + { + return 2; + } + + if (paramArrayType2 != null && paramArrayType1 == null) + { + return 1; + } + + bool p1Less = false; + bool p2Less = false; + + for (int i = 0; i < types.Length; i++) + { + if (args != null && args[i] == Type.Missing) + { + continue; + } + + Type c1, c2; + + // If a param array is present, then either + // the user re-ordered the parameters in which case + // the argument to the param array is either an array + // in which case the params is conceptually ignored and so paramArrayType1 == null + // or the argument to the param array is a single element + // in which case paramOrder[i] == p1.Length - 1 for that element + // or the user did not re-order the parameters in which case + // the paramOrder array could contain indexes larger than p.Length - 1 + //// so any index >= p.Length - 1 is being put in the param array + + if (paramArrayType1 != null && paramOrder1[i] >= p1.Length - 1) + { + c1 = paramArrayType1; + } + else + { + c1 = p1[paramOrder1[i]].ParameterType; + } + + if (paramArrayType2 != null && paramOrder2[i] >= p2.Length - 1) + { + c2 = paramArrayType2; + } + else + { + c2 = p2[paramOrder2[i]].ParameterType; + } + + if (c1 == c2) + { + continue; + } + + if (c1.ContainsGenericParameters || c2.ContainsGenericParameters) + { + continue; + } + + switch (FindMostSpecificType(c1, c2, types[i])) + { + case 0: + return 0; + case 1: + p1Less = true; + break; + case 2: + p2Less = true; + break; + } + } + + // Two way p1Less and p2Less can be equal. All the arguments are the + // same they both equal false, otherwise there were things that both + // were the most specific type on.... + if (p1Less == p2Less) + { + // it's possible that the 2 methods have same sig and default param in which case we match the one + // with the same number of args but only if they were exactly the same (that is p1Less and p2Lees are both false) + if (!p1Less && p1.Length != p2.Length && args != null) + { + if (p1.Length == args.Length) + { + return 1; + } + else if (p2.Length == args.Length) + { + return 2; + } + } + + return 0; + } + else + { + return (p1Less == true) ? 1 : 2; + } + } + + /// + /// Finds the most specific type in the two provided. + /// + /// Type 1 + /// Type 2 + /// The defining type + /// An int representing the match. + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")] + internal static int FindMostSpecificType(Type c1, Type c2, Type t) + { + // If the two types are exact move on... + if (c1 == c2) + { + return 0; + } + + if (c1 == t) + { + return 1; + } + + if (c2 == t) + { + return 2; + } + + bool c1FromC2; + bool c2FromC1; + + if (c1.IsByRef || c2.IsByRef) + { + if (c1.IsByRef && c2.IsByRef) + { + c1 = c1.GetElementType(); + c2 = c2.GetElementType(); + } + else if (c1.IsByRef) + { + if (c1.GetElementType() == c2) + { + return 2; + } + + c1 = c1.GetElementType(); + } + else + { + if (c2.GetElementType() == c1) + { + return 1; + } + + c2 = c2.GetElementType(); + } + } + + if (c1.IsPrimitive && c2.IsPrimitive) + { + c1FromC2 = true; + c2FromC1 = true; + } + else + { + c1FromC2 = c1.IsAssignableFrom(c2); + c2FromC1 = c2.IsAssignableFrom(c1); + } + + if (c1FromC2 == c2FromC1) + { + return 0; + } + + if (c1FromC2) + { + return 2; + } + else + { + return 1; + } + } + } +} diff --git a/boilersGraphics.Test/boilersGraphics.Test.csproj b/boilersGraphics.Test/boilersGraphics.Test.csproj index a82b1011..2d0fb6f8 100644 --- a/boilersGraphics.Test/boilersGraphics.Test.csproj +++ b/boilersGraphics.Test/boilersGraphics.Test.csproj @@ -93,6 +93,10 @@ + + + + From f48e1e97d38b65b91aa31c2f9fc0bdf2963242e1 Mon Sep 17 00:00:00 2001 From: dhq_boiler Date: Sat, 2 Oct 2021 16:20:08 +0900 Subject: [PATCH 4/7] add test --- boilersGraphics.Test/AdornerTest.cs | 31 +++++++++++++++++++ .../boilersGraphics.Test.csproj | 1 + .../Adorners/BezierCurveAdorner.cs | 2 +- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 boilersGraphics.Test/AdornerTest.cs diff --git a/boilersGraphics.Test/AdornerTest.cs b/boilersGraphics.Test/AdornerTest.cs new file mode 100644 index 00000000..e525d814 --- /dev/null +++ b/boilersGraphics.Test/AdornerTest.cs @@ -0,0 +1,31 @@ +using boilersGraphics.Adorners; +using boilersGraphics.Controls; +using boilersGraphics.ViewModels; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; + +namespace boilersGraphics.Test +{ + [TestFixture] + public class AdornerTest + { + [Test, RequiresThread(System.Threading.ApartmentState.STA)] + public void BezierCurveAdornerを作成() + { + var diagramViewModel = new DiagramViewModel(new MainWindowViewModel(null), 100, 100); + var designerCanvas = new DesignerCanvas(); + designerCanvas.DataContext = diagramViewModel; + diagramViewModel.EdgeColors.Add(Colors.Red); + diagramViewModel.EdgeThickness.Value = 1.0; + var adorner = new BezierCurveAdorner(designerCanvas, new System.Windows.Point() { X = 100, Y = 100 }); + var po = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(adorner); + Assert.That(po.GetField("_designerCanvas"), Is.EqualTo(designerCanvas)); + Assert.That(po.GetField("_startPoint"), Is.EqualTo(new System.Windows.Point() { X = 100, Y = 100 })); + } + } +} diff --git a/boilersGraphics.Test/boilersGraphics.Test.csproj b/boilersGraphics.Test/boilersGraphics.Test.csproj index 2d0fb6f8..ff4e140f 100644 --- a/boilersGraphics.Test/boilersGraphics.Test.csproj +++ b/boilersGraphics.Test/boilersGraphics.Test.csproj @@ -86,6 +86,7 @@ + diff --git a/boilersGraphics/Adorners/BezierCurveAdorner.cs b/boilersGraphics/Adorners/BezierCurveAdorner.cs index 085b9c3b..562cc60c 100644 --- a/boilersGraphics/Adorners/BezierCurveAdorner.cs +++ b/boilersGraphics/Adorners/BezierCurveAdorner.cs @@ -16,7 +16,7 @@ namespace boilersGraphics.Adorners { - class BezierCurveAdorner : Adorner + public class BezierCurveAdorner : Adorner { private DesignerCanvas _designerCanvas; private Point? _startPoint; From ecdb763c80061ea98729e78472694685346a8281 Mon Sep 17 00:00:00 2001 From: dhq_boiler Date: Sat, 2 Oct 2021 16:58:37 +0900 Subject: [PATCH 5/7] refactor --- boilersGraphics/Helpers/BrushInternal.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/boilersGraphics/Helpers/BrushInternal.cs b/boilersGraphics/Helpers/BrushInternal.cs index c2ccb4e6..04b3c796 100644 --- a/boilersGraphics/Helpers/BrushInternal.cs +++ b/boilersGraphics/Helpers/BrushInternal.cs @@ -48,7 +48,7 @@ public static void Draw(MainWindowViewModel mainWindowViewModel, ref BrushViewMo public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas AssociatedObject, ref BrushViewModel currentBrush, MouseButtonEventArgs e, Point point) { - var selectedDataContext = (App.Current.MainWindow.DataContext as MainWindowViewModel).DiagramViewModel.AllItems.Value.Where(x => x.IsSelected.Value == true).Select(x => x); + var selectedDataContext = mainWindowViewModel.DiagramViewModel.AllItems.Value.Where(x => x.IsSelected.Value == true).Select(x => x); var input = AssociatedObject.InputHitTest(point); if (selectedDataContext.Count() > 0) { @@ -83,7 +83,7 @@ public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas AssociatedObject, ref BrushViewModel currentBrush, StylusDownEventArgs e, Point point) { - var selectedDataContext = (App.Current.MainWindow.DataContext as MainWindowViewModel).DiagramViewModel.AllItems.Value.Where(x => x.IsSelected.Value == true).Select(x => x); + var selectedDataContext = mainWindowViewModel.DiagramViewModel.AllItems.Value.Where(x => x.IsSelected.Value == true).Select(x => x); var input = AssociatedObject.InputHitTest(point); if (selectedDataContext.Count() > 0) { @@ -115,7 +115,7 @@ public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas AssociatedObject, ref BrushViewModel currentBrush, TouchEventArgs e, Point point) { - var selectedDataContext = (App.Current.MainWindow.DataContext as MainWindowViewModel).DiagramViewModel.AllItems.Value.Where(x => x.IsSelected.Value == true).Select(x => x); + var selectedDataContext = mainWindowViewModel.DiagramViewModel.AllItems.Value.Where(x => x.IsSelected.Value == true).Select(x => x); var input = AssociatedObject.InputHitTest(point); if (selectedDataContext.Count() > 0) { From 7fb16a1fc1436e17a4c39a785dd15f461222197f Mon Sep 17 00:00:00 2001 From: dhq_boiler Date: Sat, 2 Oct 2021 18:42:17 +0900 Subject: [PATCH 6/7] add tests --- boilersGraphics.Test/HelpersTest.cs | 174 +++++++++++++++++- boilersGraphics/Helpers/BrushInternal.cs | 74 +------- boilersGraphics/Helpers/EraserInternal.cs | 40 +--- .../Views/Behaviors/BrushBehavior.cs | 6 +- 4 files changed, 186 insertions(+), 108 deletions(-) diff --git a/boilersGraphics.Test/HelpersTest.cs b/boilersGraphics.Test/HelpersTest.cs index e3da5543..8c043f63 100644 --- a/boilersGraphics.Test/HelpersTest.cs +++ b/boilersGraphics.Test/HelpersTest.cs @@ -1,4 +1,6 @@ -using boilersGraphics.Helpers; +using boilersGraphics.Controls; +using boilersGraphics.Helpers; +using boilersGraphics.Models; using boilersGraphics.ViewModels; using NUnit.Framework; using System; @@ -6,6 +8,9 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; namespace boilersGraphics.Test { @@ -45,5 +50,172 @@ public void GeometryCreator_CreateEllipse_Angle() Assert.That(pg.Bounds.Width, Is.EqualTo(10)); Assert.That(pg.Bounds.Height, Is.EqualTo(20)); } + + [Test, RequiresThread(System.Threading.ApartmentState.STA)] + public void BrushInternal_AddNewBrushViewModel() + { + var diagramViewModel = new DiagramViewModel(new MainWindowViewModel(null), 100, 100); + var desingerCanvas = new DesignerCanvas(); + desingerCanvas.DataContext = diagramViewModel; + diagramViewModel.FillColors.Add(Colors.Red); + diagramViewModel.EdgeColors.Add(Colors.Transparent); + diagramViewModel.EdgeThickness.Value = 1.0; + diagramViewModel.Layers.Clear(); + var layer1 = new Layer(); + layer1.Name.Value = "レイヤー1"; + diagramViewModel.Layers.Add(layer1); + layer1.IsSelected.Value = true; + + BrushViewModel vm = new BrushViewModel(); + + BrushInternal.AddNewBrushViewModel(desingerCanvas, ref vm, new System.Windows.Point() { X = 100, Y = 100 }); + + Assert.That(vm.Width.Value, Is.EqualTo(100)); + Assert.That(vm.Height.Value, Is.EqualTo(100)); + Assert.That(vm.EdgeColor.Value, Is.EqualTo(Colors.Transparent)); + Assert.That(vm.FillColor.Value, Is.EqualTo(Colors.Red)); + } + + [Test, RequiresThread(System.Threading.ApartmentState.STA)] + public void BrushInternal_Draw() + { + var mainWindowViewModel = new MainWindowViewModel(null); + var diagramViewModel = new DiagramViewModel(mainWindowViewModel, 100, 100); + var desingerCanvas = new DesignerCanvas(); + desingerCanvas.DataContext = diagramViewModel; + diagramViewModel.FillColors.Add(Colors.Red); + diagramViewModel.EdgeColors.Add(Colors.Transparent); + diagramViewModel.EdgeThickness.Value = 1.0; + diagramViewModel.Layers.Clear(); + var layer1 = new Layer(); + layer1.Name.Value = "レイヤー1"; + diagramViewModel.Layers.Add(layer1); + layer1.IsSelected.Value = true; + + BrushViewModel vm = new BrushViewModel(); + + BrushInternal.AddNewBrushViewModel(desingerCanvas, ref vm, new System.Windows.Point() { X = 100, Y = 100 }); + + BrushInternal.Draw(mainWindowViewModel, ref vm, new System.Windows.Point() { X = 100, Y = 100 }); + + Assert.That(vm.PathGeometry.Value.ToString(), Is.EqualTo("F1M99,97C99.5522842407227,97 100.052284240723,97.2238616943359 100.414215087891,97.5857849121094 100.776138305664,97.9477157592773 101,98.4477157592773 101,99 101,99.5522842407227 100.776138305664,100.052284240723 100.414215087891,100.414215087891C100.052284240723,100.776138305664,99.5522842407227,101,99,101C98.4477157592773,101,97.9477157592773,100.776138305664,97.5857849121094,100.414215087891C97.2238616943359,100.052284240723,97,99.5522842407227,97,99C97,98.4477157592773,97.2238616943359,97.9477157592773,97.5857849121094,97.5857849121094C97.9477157592773,97.2238616943359,98.4477157592773,97,99,97z")); + } + + [Test, RequiresThread(System.Threading.ApartmentState.STA)] + public void BrushInternal_Down() + { + var mainWindowViewModel = new MainWindowViewModel(null); + var diagramViewModel = new DiagramViewModel(mainWindowViewModel, 100, 100); + var designerCanvas = new DesignerCanvas(); + designerCanvas.DataContext = diagramViewModel; + diagramViewModel.FillColors.Add(Colors.Red); + diagramViewModel.EdgeColors.Add(Colors.Transparent); + diagramViewModel.EdgeThickness.Value = 1.0; + diagramViewModel.Layers.Clear(); + var layer1 = new Layer(); + layer1.Name.Value = "レイヤー1"; + diagramViewModel.Layers.Add(layer1); + layer1.IsSelected.Value = true; + + BrushViewModel vm = new BrushViewModel(); + + BrushInternal.Down(mainWindowViewModel, designerCanvas, ref vm, () => new System.Windows.Input.MouseButtonEventArgs(InputManager.Current.PrimaryMouseDevice, 0, MouseButton.Left).MouseDevice.Capture(designerCanvas), new System.Windows.Input.MouseButtonEventArgs(InputManager.Current.PrimaryMouseDevice, 0, MouseButton.Left), new System.Windows.Point() { X = 50, Y = 50 }); + + Assert.That(vm.PathGeometry.Value.ToString(), Is.EqualTo("M51,49C51,50.1045694996616 50.1045694996616,51 49,51 47.8954305003384,51 47,50.1045694996616 47,49 47,47.8954305003384 47.8954305003384,47 49,47 50.1045694996616,47 51,47.8954305003384 51,49z")); + } + + [Test, RequiresThread(System.Threading.ApartmentState.STA)] + public void BrushInternal_Down_2回目() + { + var mainWindowViewModel = new MainWindowViewModel(null); + var diagramViewModel = new DiagramViewModel(mainWindowViewModel, 100, 100); + var designerCanvas = new DesignerCanvas(); + designerCanvas.DataContext = diagramViewModel; + diagramViewModel.FillColors.Add(Colors.Red); + diagramViewModel.EdgeColors.Add(Colors.Transparent); + diagramViewModel.EdgeThickness.Value = 1.0; + diagramViewModel.Layers.Clear(); + var layer1 = new Layer(); + layer1.Name.Value = "レイヤー1"; + diagramViewModel.Layers.Add(layer1); + layer1.IsSelected.Value = true; + + BrushViewModel vm = new BrushViewModel(); + + BrushInternal.Down(mainWindowViewModel, designerCanvas, ref vm, () => new System.Windows.Input.MouseButtonEventArgs(InputManager.Current.PrimaryMouseDevice, 0, MouseButton.Left).MouseDevice.Capture(designerCanvas), new System.Windows.Input.MouseButtonEventArgs(InputManager.Current.PrimaryMouseDevice, 0, MouseButton.Left), new System.Windows.Point() { X = 50, Y = 50 }) ; + + Assert.That(vm.PathGeometry.Value.ToString(), Is.EqualTo("M51,49C51,50.1045694996616 50.1045694996616,51 49,51 47.8954305003384,51 47,50.1045694996616 47,49 47,47.8954305003384 47.8954305003384,47 49,47 50.1045694996616,47 51,47.8954305003384 51,49z")); + + mainWindowViewModel.DiagramViewModel.AllItems.Value.First().IsSelected.Value = true; + + BrushInternal.Down(mainWindowViewModel, designerCanvas, ref vm, () => new System.Windows.Input.MouseButtonEventArgs(InputManager.Current.PrimaryMouseDevice, 0, MouseButton.Left).MouseDevice.Capture(designerCanvas), new System.Windows.Input.MouseButtonEventArgs(InputManager.Current.PrimaryMouseDevice, 0, MouseButton.Left), new System.Windows.Point() { X = 100, Y = 100 }); + + Assert.That(vm.PathGeometry.Value.ToString(), Is.EqualTo("M101,99C101,100.104569499662 100.104569499662,101 99,101 97.8954305003384,101 97,100.104569499662 97,99 97,97.8954305003384 97.8954305003384,97 99,97 100.104569499662,97 101,97.8954305003384 101,99z")); + } + + // + + [Test, RequiresThread(System.Threading.ApartmentState.STA)] + public void EraserInternal_Erase() + { + var mainWindowViewModel = new MainWindowViewModel(null); + var diagramViewModel = new DiagramViewModel(mainWindowViewModel, 100, 100); + var desingerCanvas = new DesignerCanvas(); + desingerCanvas.DataContext = diagramViewModel; + diagramViewModel.FillColors.Add(Colors.Red); + diagramViewModel.EdgeColors.Add(Colors.Transparent); + diagramViewModel.EdgeThickness.Value = 1.0; + diagramViewModel.Layers.Clear(); + var layer1 = new Layer(); + layer1.Name.Value = "レイヤー1"; + diagramViewModel.Layers.Add(layer1); + layer1.IsSelected.Value = true; + + BrushViewModel vm = new BrushViewModel(); + + BrushInternal.AddNewBrushViewModel(desingerCanvas, ref vm, new System.Windows.Point() { X = 100, Y = 100 }); + + BrushInternal.Draw(mainWindowViewModel, ref vm, new System.Windows.Point() { X = 100, Y = 100 }); + + EraserInternal.Erase(mainWindowViewModel, ref vm, new System.Windows.Point() { X = 100, Y = 100 }); + + Assert.That(vm.PathGeometry.Value.ToString(), Is.EqualTo("F1M101,99C101,99.5522842407227,100.776138305664,100.052284240723,100.414215087891,100.414215087891C100.052284240723,100.776138305664,99.5522842407227,101,99,101C100.104568481445,101,101,100.104568481445,101,99z M97,99C97,100.104568481445,97.8954315185547,101,99,101C98.4477157592773,101,97.9477157592773,100.776138305664,97.5857849121094,100.414215087891C97.2238616943359,100.052284240723,97,99.5522842407227,97,99z M99,97C97.8954315185547,97 97,97.8954315185547 97,99 97,98.4477157592773 97.2238616943359,97.9477157592773 97.5857849121094,97.5857849121094C97.9477157592773,97.2238616943359,98.4477157592773,97,99,97z M99,97C99.5522842407227,97 100.052284240723,97.2238616943359 100.414215087891,97.5857849121094 100.776138305664,97.9477157592773 101,98.4477157592773 101,99C101,97.8954315185547,100.104568481445,97,99,97z")); + } + + [Test, RequiresThread(System.Threading.ApartmentState.STA)] + public void EraserInternal_Down() + { + var mainWindowViewModel = new MainWindowViewModel(null); + var diagramViewModel = mainWindowViewModel.DiagramViewModel; + var designerCanvas = new DesignerCanvas(); + designerCanvas.DataContext = diagramViewModel; + diagramViewModel.FillColors.Add(Colors.Red); + diagramViewModel.EdgeColors.Add(Colors.Transparent); + diagramViewModel.EdgeThickness.Value = 1.0; + diagramViewModel.Layers.Clear(); + var layer1 = new Layer(); + layer1.Name.Value = "レイヤー1"; + diagramViewModel.Layers.Add(layer1); + layer1.IsSelected.Value = true; + + BrushViewModel vm = new BrushViewModel(); + + BrushInternal.AddNewBrushViewModel(designerCanvas, ref vm, new System.Windows.Point() { X = 100, Y = 100 }); + + (designerCanvas.DataContext as DiagramViewModel).SelectedItems.Value.OfType().First().IsSelected.Value = true; + vm.IsSelected.Value = true; + + designerCanvas.Children.Add(new System.Windows.Shapes.Path() { DataContext = (designerCanvas.DataContext as DiagramViewModel).SelectedItems.Value.OfType().First() }); + + BrushInternal.Draw(mainWindowViewModel, ref vm, new System.Windows.Point() { X = 100, Y = 100 }); + + EraserInternal.Down(mainWindowViewModel, + designerCanvas, + ref vm, + new System.Windows.Input.MouseButtonEventArgs(InputManager.Current.PrimaryMouseDevice, 0, MouseButton.Left) { RoutedEvent = Mouse.MouseDownEvent }, + new Point() { X = 100, Y = 100 }); + + Assert.That(vm.PathGeometry.Value.ToString(), Is.EqualTo("F1M101,99C101,99.5522842407227,100.776138305664,100.052284240723,100.414215087891,100.414215087891C100.052284240723,100.776138305664,99.5522842407227,101,99,101C100.104568481445,101,101,100.104568481445,101,99z M97,99C97,100.104568481445,97.8954315185547,101,99,101C98.4477157592773,101,97.9477157592773,100.776138305664,97.5857849121094,100.414215087891C97.2238616943359,100.052284240723,97,99.5522842407227,97,99z M99,97C97.8954315185547,97 97,97.8954315185547 97,99 97,98.4477157592773 97.2238616943359,97.9477157592773 97.5857849121094,97.5857849121094C97.9477157592773,97.2238616943359,98.4477157592773,97,99,97z M99,97C99.5522842407227,97 100.052284240723,97.2238616943359 100.414215087891,97.5857849121094 100.776138305664,97.9477157592773 101,98.4477157592773 101,99C101,97.8954315185547,100.104568481445,97,99,97z")); + } } } diff --git a/boilersGraphics/Helpers/BrushInternal.cs b/boilersGraphics/Helpers/BrushInternal.cs index 04b3c796..f6b39315 100644 --- a/boilersGraphics/Helpers/BrushInternal.cs +++ b/boilersGraphics/Helpers/BrushInternal.cs @@ -2,6 +2,7 @@ using boilersGraphics.Extensions; using boilersGraphics.Models; using boilersGraphics.ViewModels; +using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; @@ -46,16 +47,15 @@ public static void Draw(MainWindowViewModel mainWindowViewModel, ref BrushViewMo mainWindowViewModel.Recorder.Current.ExecuteSetProperty(currentBrush, "PathGeometry.Value", Geometry.Combine(currentBrush.PathGeometry.Value, GeometryCreator.CreateEllipse(point.X, point.Y, currentBrush.Thickness.Value), GeometryCombineMode.Union, null)); } - public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas AssociatedObject, ref BrushViewModel currentBrush, MouseButtonEventArgs e, Point point) + public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas AssociatedObject, ref BrushViewModel currentBrush, Action captureAction, RoutedEventArgs e, Point point) { var selectedDataContext = mainWindowViewModel.DiagramViewModel.AllItems.Value.Where(x => x.IsSelected.Value == true).Select(x => x); - var input = AssociatedObject.InputHitTest(point); if (selectedDataContext.Count() > 0) { - var views = App.Current.MainWindow.GetChildOfType().GetCorrespondingViews(selectedDataContext.First()).Where(x => x.GetType() == selectedDataContext.First().GetViewType()); + var views = AssociatedObject.GetCorrespondingViews(selectedDataContext.First()).Where(x => x.GetType() == selectedDataContext.First().GetViewType()); if (!views.Any()) { - e.MouseDevice.Capture(AssociatedObject); + captureAction.Invoke(); currentBrush = BrushViewModel.CreateInstance(); AddNewBrushViewModel(AssociatedObject, ref currentBrush, point); } @@ -75,74 +75,10 @@ public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas } else { - e.MouseDevice.Capture(AssociatedObject); + captureAction.Invoke(); currentBrush = BrushViewModel.CreateInstance(); AddNewBrushViewModel(AssociatedObject, ref currentBrush, point); } } - - public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas AssociatedObject, ref BrushViewModel currentBrush, StylusDownEventArgs e, Point point) - { - var selectedDataContext = mainWindowViewModel.DiagramViewModel.AllItems.Value.Where(x => x.IsSelected.Value == true).Select(x => x); - var input = AssociatedObject.InputHitTest(point); - if (selectedDataContext.Count() > 0) - { - var views = App.Current.MainWindow.GetChildOfType().GetCorrespondingViews(selectedDataContext.First()).Where(x => x.GetType() == selectedDataContext.First().GetViewType()); - if (!views.Any()) - { - e.StylusDevice.Capture(AssociatedObject); - AddNewBrushViewModel(AssociatedObject, ref currentBrush, point); - } - else - { - if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) - { - BrushInternal.Draw(mainWindowViewModel, ref currentBrush, point, views); - } - else - { - AddNewBrushViewModel(AssociatedObject, ref currentBrush, point); - } - e.Handled = true; - } - } - else - { - e.StylusDevice.Capture(AssociatedObject); - AddNewBrushViewModel(AssociatedObject, ref currentBrush, point); - } - } - - public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas AssociatedObject, ref BrushViewModel currentBrush, TouchEventArgs e, Point point) - { - var selectedDataContext = mainWindowViewModel.DiagramViewModel.AllItems.Value.Where(x => x.IsSelected.Value == true).Select(x => x); - var input = AssociatedObject.InputHitTest(point); - if (selectedDataContext.Count() > 0) - { - var views = App.Current.MainWindow.GetChildOfType().GetCorrespondingViews(selectedDataContext.First()).Where(x => x.GetType() == selectedDataContext.First().GetViewType()); - if (!views.Any()) - { - e.TouchDevice.Capture(AssociatedObject); - AddNewBrushViewModel(AssociatedObject, ref currentBrush, point); - } - else - { - if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) - { - Draw(mainWindowViewModel, ref currentBrush, point, views); - } - else - { - AddNewBrushViewModel(AssociatedObject, ref currentBrush, point); - } - e.Handled = true; - } - } - else - { - e.TouchDevice.Capture(AssociatedObject); - AddNewBrushViewModel(AssociatedObject, ref currentBrush, point); - } - } } } diff --git a/boilersGraphics/Helpers/EraserInternal.cs b/boilersGraphics/Helpers/EraserInternal.cs index 0c264a39..c3e05a9c 100644 --- a/boilersGraphics/Helpers/EraserInternal.cs +++ b/boilersGraphics/Helpers/EraserInternal.cs @@ -25,44 +25,14 @@ public static void Erase(MainWindowViewModel mainWindowViewModel, ref BrushViewM mainWindowViewModel.Recorder.Current.ExecuteSetProperty(currentBrush, "PathGeometry.Value", Geometry.Combine(currentBrush.PathGeometry.Value, GeometryCreator.CreateEllipse(point.X, point.Y, currentBrush.Thickness.Value), GeometryCombineMode.Exclude, null)); } - public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas AssociatedObject, ref BrushViewModel currentBrush, MouseButtonEventArgs e, Point point) + public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas AssociatedObject, ref BrushViewModel currentBrush, RoutedEventArgs e, Point point) { - var selectedDataContext = (App.Current.MainWindow.DataContext as MainWindowViewModel).DiagramViewModel.AllItems.Value.Where(x => x.IsSelected.Value == true).Select(x => x); - var input = AssociatedObject.InputHitTest(point); + var selectedDataContext = mainWindowViewModel.DiagramViewModel.AllItems.Value.Where(x => x.IsSelected.Value == true).Select(x => x); if (selectedDataContext.Count() > 0) { - var views = App.Current.MainWindow.GetChildOfType().GetCorrespondingViews(selectedDataContext.First()).Where(x => x.GetType() == selectedDataContext.First().GetViewType()); - if (views.Any()) - { - Erase(mainWindowViewModel, ref currentBrush, point, views); - e.Handled = true; - } - } - } - - public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas AssociatedObject, ref BrushViewModel currentBrush, StylusDownEventArgs e, Point point) - { - var selectedDataContext = (App.Current.MainWindow.DataContext as MainWindowViewModel).DiagramViewModel.AllItems.Value.Where(x => x.IsSelected.Value == true).Select(x => x); - var input = AssociatedObject.InputHitTest(point); - if (selectedDataContext.Count() > 0) - { - var views = App.Current.MainWindow.GetChildOfType().GetCorrespondingViews(selectedDataContext.First()).Where(x => x.GetType() == selectedDataContext.First().GetViewType()); - if (views.Any()) - { - Erase(mainWindowViewModel, ref currentBrush, point, views); - e.Handled = true; - } - } - } - - public static void Down(MainWindowViewModel mainWindowViewModel, DesignerCanvas AssociatedObject, ref BrushViewModel currentBrush, TouchEventArgs e, Point point) - { - var selectedDataContext = (App.Current.MainWindow.DataContext as MainWindowViewModel).DiagramViewModel.AllItems.Value.Where(x => x.IsSelected.Value == true).Select(x => x); - var input = AssociatedObject.InputHitTest(point); - if (selectedDataContext.Count() > 0) - { - var views = App.Current.MainWindow.GetChildOfType().GetCorrespondingViews(selectedDataContext.First()).Where(x => x.GetType() == selectedDataContext.First().GetViewType()); - if (views.Any()) + var views = AssociatedObject.GetCorrespondingViews(selectedDataContext.First()); + var filtered = views.Where(x => x.GetType() == selectedDataContext.First().GetViewType()); + if (filtered.Any()) { Erase(mainWindowViewModel, ref currentBrush, point, views); e.Handled = true; diff --git a/boilersGraphics/Views/Behaviors/BrushBehavior.cs b/boilersGraphics/Views/Behaviors/BrushBehavior.cs index b9e34992..a1f312bb 100644 --- a/boilersGraphics/Views/Behaviors/BrushBehavior.cs +++ b/boilersGraphics/Views/Behaviors/BrushBehavior.cs @@ -68,7 +68,7 @@ private void AssociatedObject_StylusDown(object sender, StylusDownEventArgs e) currentBrush = BrushViewModel.CreateInstance(); currentBrush.OpenThicknessDialog(); } - BrushInternal.Down((AssociatedObject.DataContext as DiagramViewModel).MainWindowVM, AssociatedObject, ref currentBrush, e, point); + BrushInternal.Down((AssociatedObject.DataContext as DiagramViewModel).MainWindowVM, AssociatedObject, ref currentBrush, () => e.StylusDevice.Capture(AssociatedObject), e, point); downFlag = true; e.Handled = true; } @@ -97,7 +97,7 @@ private void AssociatedObject_TouchDown(object sender, TouchEventArgs e) currentBrush = BrushViewModel.CreateInstance(); currentBrush.OpenThicknessDialog(); } - BrushInternal.Down((AssociatedObject.DataContext as DiagramViewModel).MainWindowVM, AssociatedObject, ref currentBrush, e, point); + BrushInternal.Down((AssociatedObject.DataContext as DiagramViewModel).MainWindowVM, AssociatedObject, ref currentBrush, () => e.TouchDevice.Capture(AssociatedObject), e, point); downFlag = true; } } @@ -126,7 +126,7 @@ private void AssociatedObject_MouseDown(object sender, System.Windows.Input.Mous currentBrush = BrushViewModel.CreateInstance(); currentBrush.OpenThicknessDialog(); } - BrushInternal.Down((AssociatedObject.DataContext as DiagramViewModel).MainWindowVM, AssociatedObject, ref currentBrush, e, point); + BrushInternal.Down((AssociatedObject.DataContext as DiagramViewModel).MainWindowVM, AssociatedObject, ref currentBrush, () => e.MouseDevice.Capture(AssociatedObject), e, point); downFlag = true; } } From 0271e04daa76a1fcb7498ef0fcef5da9b9bc2fd0 Mon Sep 17 00:00:00 2001 From: dhq_boiler Date: Sat, 2 Oct 2021 20:07:26 +0900 Subject: [PATCH 7/7] bugfix --- boilersGraphics.Test/HelpersTest.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/boilersGraphics.Test/HelpersTest.cs b/boilersGraphics.Test/HelpersTest.cs index 8c043f63..42c00366 100644 --- a/boilersGraphics.Test/HelpersTest.cs +++ b/boilersGraphics.Test/HelpersTest.cs @@ -54,10 +54,13 @@ public void GeometryCreator_CreateEllipse_Angle() [Test, RequiresThread(System.Threading.ApartmentState.STA)] public void BrushInternal_AddNewBrushViewModel() { - var diagramViewModel = new DiagramViewModel(new MainWindowViewModel(null), 100, 100); + var mainWindowViewModel = new MainWindowViewModel(null); + var diagramViewModel = mainWindowViewModel.DiagramViewModel; var desingerCanvas = new DesignerCanvas(); desingerCanvas.DataContext = diagramViewModel; + diagramViewModel.FillColors.Clear(); diagramViewModel.FillColors.Add(Colors.Red); + diagramViewModel.EdgeColors.Clear(); diagramViewModel.EdgeColors.Add(Colors.Transparent); diagramViewModel.EdgeThickness.Value = 1.0; diagramViewModel.Layers.Clear(); @@ -70,8 +73,8 @@ public void BrushInternal_AddNewBrushViewModel() BrushInternal.AddNewBrushViewModel(desingerCanvas, ref vm, new System.Windows.Point() { X = 100, Y = 100 }); - Assert.That(vm.Width.Value, Is.EqualTo(100)); - Assert.That(vm.Height.Value, Is.EqualTo(100)); + Assert.That(vm.Width.Value, Is.EqualTo(1000)); + Assert.That(vm.Height.Value, Is.EqualTo(1000)); Assert.That(vm.EdgeColor.Value, Is.EqualTo(Colors.Transparent)); Assert.That(vm.FillColor.Value, Is.EqualTo(Colors.Red)); } @@ -80,7 +83,7 @@ public void BrushInternal_AddNewBrushViewModel() public void BrushInternal_Draw() { var mainWindowViewModel = new MainWindowViewModel(null); - var diagramViewModel = new DiagramViewModel(mainWindowViewModel, 100, 100); + var diagramViewModel = mainWindowViewModel.DiagramViewModel; var desingerCanvas = new DesignerCanvas(); desingerCanvas.DataContext = diagramViewModel; diagramViewModel.FillColors.Add(Colors.Red); @@ -105,7 +108,7 @@ public void BrushInternal_Draw() public void BrushInternal_Down() { var mainWindowViewModel = new MainWindowViewModel(null); - var diagramViewModel = new DiagramViewModel(mainWindowViewModel, 100, 100); + var diagramViewModel = mainWindowViewModel.DiagramViewModel; var designerCanvas = new DesignerCanvas(); designerCanvas.DataContext = diagramViewModel; diagramViewModel.FillColors.Add(Colors.Red); @@ -128,7 +131,7 @@ public void BrushInternal_Down() public void BrushInternal_Down_2回目() { var mainWindowViewModel = new MainWindowViewModel(null); - var diagramViewModel = new DiagramViewModel(mainWindowViewModel, 100, 100); + var diagramViewModel = mainWindowViewModel.DiagramViewModel; var designerCanvas = new DesignerCanvas(); designerCanvas.DataContext = diagramViewModel; diagramViewModel.FillColors.Add(Colors.Red); @@ -153,13 +156,11 @@ public void BrushInternal_Down_2回目() Assert.That(vm.PathGeometry.Value.ToString(), Is.EqualTo("M101,99C101,100.104569499662 100.104569499662,101 99,101 97.8954305003384,101 97,100.104569499662 97,99 97,97.8954305003384 97.8954305003384,97 99,97 100.104569499662,97 101,97.8954305003384 101,99z")); } - // - [Test, RequiresThread(System.Threading.ApartmentState.STA)] public void EraserInternal_Erase() { var mainWindowViewModel = new MainWindowViewModel(null); - var diagramViewModel = new DiagramViewModel(mainWindowViewModel, 100, 100); + var diagramViewModel = mainWindowViewModel.DiagramViewModel; var desingerCanvas = new DesignerCanvas(); desingerCanvas.DataContext = diagramViewModel; diagramViewModel.FillColors.Add(Colors.Red);