From df943f924dafb874f0052e88539c59197b37964f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Philippe=20Bruy=C3=A8re?= Date: Tue, 14 Sep 2021 07:50:25 +0000 Subject: [PATCH] ToggleCommand creation --- Crow/src/Command/Command.cs | 22 +-- Crow/src/Command/CommandBase.cs | 10 +- Crow/src/Command/CommandGroup.cs | 18 ++- Crow/src/Command/ToggleCommand.cs | 137 ++++++++++++++++++ Crow/src/ExtensionsMethods.cs | 16 +- Crow/src/Widgets/Button.cs | 7 +- Crow/src/Widgets/Expandable.cs | 2 +- Crow/src/Widgets/Menu.cs | 2 +- Crow/src/Widgets/MenuItem.cs | 20 ++- Crow/src/Widgets/Widget.cs | 2 +- Directory.Build.props | 2 +- Samples/common/src/SampleBase.cs | 7 +- .../ui/Interfaces/Experimental/toggleCmd.crow | 55 +++++++ 13 files changed, 262 insertions(+), 38 deletions(-) create mode 100644 Crow/src/Command/ToggleCommand.cs create mode 100644 Samples/common/ui/Interfaces/Experimental/toggleCmd.crow diff --git a/Crow/src/Command/Command.cs b/Crow/src/Command/Command.cs index 80bbf462..6b8851e5 100644 --- a/Crow/src/Command/Command.cs +++ b/Crow/src/Command/Command.cs @@ -6,15 +6,19 @@ using System; using System.ComponentModel; using System.Threading.Tasks; -namespace Crow { +namespace Crow { /// - /// helper class to bind in one step icon, caption, action, and validity tests to a controls + /// helper class to bind in one step icon, caption, action, and validity tests to a controls /// public class Command : CommandBase { #region CTOR public Command () {} + public Command (string caption, string icon = null, bool _canExecute = true) + :base (caption, icon) + {} + /// /// Initializes a new instance of Command with the action passed as argument. /// @@ -31,23 +35,23 @@ namespace Crow { } public Command (string caption, Action executeAction, string icon = null, bool _canExecute = true) :base (caption, icon) - { + { execute = executeAction; canExecute = _canExecute; } public Command (string caption, Action executeAction, string icon = null, bool _canExecute = true) :base (caption, icon) - { + { execute = executeAction; canExecute = _canExecute; } - + #endregion - Delegate execute; + Delegate execute; bool canExecute = true; - + /// /// if true, action defined in this command may be executed, /// @@ -60,7 +64,7 @@ namespace Crow { canExecute = value; NotifyValueChanged ("CanExecute", canExecute); } - } + } /// /// trigger the execution of the command @@ -68,7 +72,7 @@ namespace Crow { public virtual void Execute (object sender = null){ if (execute != null && CanExecute){ Task task = (execute is Action a) ? - task = new Task(a) : + task = new Task(a) : (execute is Action o) ? task = new Task(o, sender) : throw new Exception("Invalid Delegate type in Crow.Command, expecting Action or Action"); task.Start(); diff --git a/Crow/src/Command/CommandBase.cs b/Crow/src/Command/CommandBase.cs index ed360333..8a01eb14 100644 --- a/Crow/src/Command/CommandBase.cs +++ b/Crow/src/Command/CommandBase.cs @@ -18,16 +18,16 @@ namespace Crow { ValueChanged.Raise(this, new ValueChangeEventArgs(MemberName, _value)); } #endregion - + #region CTOR protected CommandBase() {} protected CommandBase (string _caption, string _icon = null) { - caption = _caption; + caption = _caption; icon = _icon; } #endregion - + string caption, icon; /// @@ -46,7 +46,7 @@ namespace Crow { } /// /// Icon to display in the bound control - /// + /// public string Icon { get => icon; set { @@ -56,7 +56,7 @@ namespace Crow { NotifyValueChanged ("Icon", icon); } } - internal virtual void raiseAllValuesChanged() { + internal virtual void raiseAllValuesChanged() { NotifyValueChanged ("Icon", icon); NotifyValueChanged ("Caption", caption); } diff --git a/Crow/src/Command/CommandGroup.cs b/Crow/src/Command/CommandGroup.cs index 039f1a49..afe13e6c 100644 --- a/Crow/src/Command/CommandGroup.cs +++ b/Crow/src/Command/CommandGroup.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; namespace Crow { public class CommandGroup : CommandBase, IEnumerable, IList @@ -25,7 +26,7 @@ namespace Crow { Commands = new ObservableList(commands); } - + public int Count => Commands.Count; public bool IsReadOnly => false; @@ -46,11 +47,24 @@ namespace Crow { public bool Contains(CommandBase item) => Commands.Contains (item); - public void CopyTo(CommandBase[] array, int arrayIndex) => Commands.CopyTo (array, arrayIndex); + public void CopyTo(CommandBase[] array, int arrayIndex) => Commands.CopyTo (array, arrayIndex); public bool Remove(CommandBase item) => Commands.Remove (item); IEnumerator IEnumerable.GetEnumerator() => Commands.GetEnumerator(); + public void Add (params CommandBase[] items) { + foreach (CommandBase c in items) + Commands.Add (c); + } + public void Remove (params CommandBase[] items) { + foreach (CommandBase c in items) + Commands.Remove (c); + } + /// + public void ToggleAllCommand (bool canExecute) { + foreach (Command c in Commands.OfType ()) + c.CanExecute = canExecute; + } } } diff --git a/Crow/src/Command/ToggleCommand.cs b/Crow/src/Command/ToggleCommand.cs new file mode 100644 index 00000000..42a44768 --- /dev/null +++ b/Crow/src/Command/ToggleCommand.cs @@ -0,0 +1,137 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) + +using System; +using System.ComponentModel; +using System.Reflection; +using System.Linq; +using System.Threading.Tasks; +using System.Reflection.Emit; + +namespace Crow { + /// + /// helper class to bind in one step icon, caption, action, and validity tests to a controls + /// + public class ToggleCommand : Command, IToggle, IDisposable + { + object instance; + string memberName; + Action delSet; + Func delGet; + + #region CTOR + public ToggleCommand () {} + public ToggleCommand (object instance, string memberName, string icon = null, bool _canExecute = true) + :this ("", instance, memberName, icon, _canExecute) { + } + public ToggleCommand (string caption, object instance, string memberName, string icon = null, bool _canExecute = true) + :base (caption, icon, _canExecute) + { + this.instance = instance; + this.memberName = memberName; + MemberInfo mbi = instance.GetType().GetMember (memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(); + + if (mbi is PropertyInfo pi) { + delSet = (Action)Delegate.CreateDelegate (typeof (Action), instance, pi.GetSetMethod ()); + delGet = (Func)Delegate.CreateDelegate (typeof (Func), instance, pi.GetGetMethod ()); + } else if (mbi is FieldInfo fi) { + DynamicMethod dm = new DynamicMethod($"{fi.ReflectedType.FullName}_fldset", null, new Type[] { fi.ReflectedType, typeof(bool) }, false); + ILGenerator il = dm.GetILGenerator (); + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Ldarg_1); + il.Emit (OpCodes.Stfld, fi); + + il.Emit(OpCodes.Ret); + + delSet = (Action)dm.CreateDelegate (typeof (Action), instance); + + dm = new DynamicMethod($"{fi.ReflectedType.FullName}_fldget", typeof(bool), new Type[] { fi.ReflectedType }, false); + il = dm.GetILGenerator (); + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Ldfld, fi); + + il.Emit(OpCodes.Ret); + + delGet = (Func)dm.CreateDelegate (typeof (Func), instance); + } else + throw new Exception ("unsupported member type for ToggleCommand"); + + if (instance is IValueChange ivc) + ivc.ValueChanged += instance_valueChanged; + + CanExecute = _canExecute; + } + void instance_valueChanged (object sender, ValueChangeEventArgs e) { + if (e.MemberName != memberName) + return; + Console.WriteLine ($"ToggleCommand valueChanged triggered => {e.NewValue}"); + + bool tog = (bool)e.NewValue; + NotifyValueChanged ("IsToggled", tog); + if (tog) + ToggleOn.Raise (this, null); + else + ToggleOff.Raise (this, null); + + } + #endregion + + /// + /// trigger the execution of the command + /// + public override void Execute (object sender = null){ + if (CanExecute) + IsToggled = !IsToggled; + } + + internal override void raiseAllValuesChanged() + { + base.raiseAllValuesChanged(); + NotifyValueChanged ("IsToggled", IsToggled); + } + + #region IToggle implementation + + public event EventHandler ToggleOn; + public event EventHandler ToggleOff; + public BooleanTestOnInstance IsToggleable {get; set; } + public bool IsToggled { + get => delGet (); + set { + if (value == IsToggled) + return; + delSet (value); + NotifyValueChanged ("IsToggled", IsToggled); + Console.WriteLine ($"ToggleCommand.IsToggled => {value}"); + + if (IsToggled) + ToggleOn.Raise (this, null); + else + ToggleOff.Raise (this, null); + } + } + #endregion + + bool disposedValue; + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + if (instance is IValueChange ivc) + ivc.ValueChanged -= instance_valueChanged; + } + disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Crow/src/ExtensionsMethods.cs b/Crow/src/ExtensionsMethods.cs index bc926877..5bd9382e 100644 --- a/Crow/src/ExtensionsMethods.cs +++ b/Crow/src/ExtensionsMethods.cs @@ -53,7 +53,7 @@ namespace Crow } public static void DrawCote(this Context ctx, PointD p1, PointD p2, double stroke = 1.0, bool fill = false, double arrowWidth = 3.0, double arrowLength = 7.0) - { + { PointD vDir = p2.Substract(p1); vDir = vDir.GetNormalized (); PointD vPerp = vDir.GetPerp (); @@ -69,7 +69,7 @@ namespace Crow ctx.LineTo (pA0.Substract (vA)); else ctx.MoveTo (pA0.Substract (vA)); - + ctx.LineTo (p1); ctx.MoveTo (p2); @@ -90,7 +90,7 @@ namespace Crow } public static void DrawCoteInverse(this Context ctx, PointD p1, PointD p2, double stroke = 1.0, bool fill = false, double arrowWidth = 3.0, double arrowLength = 7.0) - { + { PointD vDir = p2.Substract(p1); vDir = vDir.GetNormalized (); PointD vPerp = vDir.GetPerp (); @@ -122,7 +122,7 @@ namespace Crow } public static void DrawCoteFixed(this Context ctx, PointD p1, PointD p2, double stroke = 1.0, double coteWidth = 3.0) - { + { PointD vDir = p2.Substract(p1); vDir = vDir.GetNormalized (); PointD vPerp = vDir.GetPerp (); @@ -164,7 +164,7 @@ namespace Crow case Alignment.BottomLeft: return Alignment.TopRight; case Alignment.BottomRight: - return Alignment.TopLeft; + return Alignment.TopLeft; } return Alignment.Center; } @@ -187,11 +187,11 @@ namespace Crow return c == '\t' || c.IsAnyLineBreakCharacter() || char.IsWhiteSpace (c); } public static object GetDefaultValue(this object obj) - { + { Type t = obj.GetType (); if (t.IsValueType) return Activator.CreateInstance (t); - + return null; } @@ -203,7 +203,7 @@ namespace Crow } } - internal static bool IsAnyLineBreakCharacter (this char c) + internal static bool IsAnyLineBreakCharacter (this char c) => c == '\n' || c == '\r' || c == '\u0085' || c == '\u2028' || c == '\u2029'; public static bool TryGetResource (this Assembly a, string resId, out Stream stream) { diff --git a/Crow/src/Widgets/Button.cs b/Crow/src/Widgets/Button.cs index 009aed78..6c005629 100644 --- a/Crow/src/Widgets/Button.cs +++ b/Crow/src/Widgets/Button.cs @@ -119,6 +119,11 @@ namespace Crow } NotifyValueChanged (mName, e.NewValue); } - + protected override void Dispose(bool disposing) + { + if (command is IDisposable dis) + dis.Dispose (); + base.Dispose(disposing); + } } } diff --git a/Crow/src/Widgets/Expandable.cs b/Crow/src/Widgets/Expandable.cs index 1e334c2e..95e743ea 100644 --- a/Crow/src/Widgets/Expandable.cs +++ b/Crow/src/Widgets/Expandable.cs @@ -85,7 +85,7 @@ namespace Crow bool isExp = IsExpandable; NotifyValueChanged ("IsExpandable", isExp); if (!isExp) - _isExpanded = false; + _isExpanded = false; NotifyValueChangedAuto (_isExpanded); NotifyValueChanged ("IsToggled",_isExpanded); diff --git a/Crow/src/Widgets/Menu.cs b/Crow/src/Widgets/Menu.cs index 1abf7dd4..6b446bbe 100644 --- a/Crow/src/Widgets/Menu.cs +++ b/Crow/src/Widgets/Menu.cs @@ -39,7 +39,7 @@ namespace Crow { #endregion public override void AddItem (Widget g) - { + { base.AddItem (g); if (orientation == Orientation.Horizontal) diff --git a/Crow/src/Widgets/MenuItem.cs b/Crow/src/Widgets/MenuItem.cs index b00101a5..c861441f 100644 --- a/Crow/src/Widgets/MenuItem.cs +++ b/Crow/src/Widgets/MenuItem.cs @@ -62,17 +62,17 @@ namespace Crow NotifyValueChangedAuto (command); } } - + public override bool IsEnabled { get => Command == null ? base.IsEnabled : Command.CanExecute; set => base.IsEnabled = value; } - + public override string Caption { get => Command == null ? base.Caption : Command.Caption; set => base.Caption = value; } - + public string Icon { get => Command == null ? icon : Command.Icon; set { @@ -121,12 +121,12 @@ namespace Crow protected virtual void onOpen (object sender, EventArgs e){ Open.Raise (this, null); } - protected virtual void onClose (object sender, EventArgs e){ + protected virtual void onClose (object sender, EventArgs e){ Close.Raise (this, null); } public override bool MouseIsIn (Point m) => IsEnabled && !IsDragged ? base.MouseIsIn (m) || child.MouseIsIn (m) : false; - + public override void onMouseEnter (object sender, MouseMoveEventArgs e) { base.onMouseEnter (sender, e); @@ -151,10 +151,10 @@ namespace Crow if (hasClick) base.onMouseClick (sender, e); - if (!IsOpened) + if (!IsOpened) if (LogicalParent is Menu m) m.AutomaticOpening = false; - + } void closeMenu () { MenuItem tmp = LogicalParent as MenuItem; @@ -165,6 +165,12 @@ namespace Crow tmp = tmp.LogicalParent as MenuItem; } } + protected override void Dispose(bool disposing) + { + if (command is IDisposable dis) + dis.Dispose (); + base.Dispose(disposing); + } } } diff --git a/Crow/src/Widgets/Widget.cs b/Crow/src/Widgets/Widget.cs index 512d27e0..4dbcb340 100644 --- a/Crow/src/Widgets/Widget.cs +++ b/Crow/src/Widgets/Widget.cs @@ -2334,7 +2334,7 @@ namespace Crow LastSlots = default; LastPaintedSlot = default;*/ //Slot = LastSlots = LastPaintedSlot = default; - Slot = LastPaintedSlot = default; + //Slot = LastPaintedSlot = default; } } } diff --git a/Directory.Build.props b/Directory.Build.props index fe1f22e3..a0ea5fb5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ Jean-Philippe Bruyère 7.3 - 0.9.6 + 0.9.7 $(CrowVersion)-beta + + + + + + + + + + + \ No newline at end of file -- 2.47.3