support for .NET framework 3.5
Still have bugs
This commit is contained in:
1 parent
5878039e21
commit
7d6b491f25
704 files changed
+48273
-33
No files matched your search
@@ -0,0 +1,40 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public class AdornerContainer : Adorner
|
||||
{
|
||||
private UIElement _child;
|
||||
|
||||
public AdornerContainer(UIElement adornedElement) : base(adornedElement)
|
||||
{
|
||||
}
|
||||
|
||||
public UIElement Child
|
||||
{
|
||||
get =>
|
||||
_child;
|
||||
set
|
||||
{
|
||||
AddVisualChild(value);
|
||||
_child = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override int VisualChildrenCount => _child != null ? 1 : 0;
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
_child?.Arrange(new Rect(finalSize));
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
protected override Visual GetVisualChild(int index)
|
||||
{
|
||||
if (index == 0 && _child != null) return _child;
|
||||
return base.GetVisualChild(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public abstract class AttachableCollection<T> : FreezableCollection<T>, IAttachedObject
|
||||
where T : DependencyObject, IAttachedObject
|
||||
{
|
||||
private DependencyObject _associatedObject;
|
||||
private Collection<T> _snapshot;
|
||||
|
||||
internal AttachableCollection()
|
||||
{
|
||||
INotifyCollectionChanged changed = this;
|
||||
changed.CollectionChanged += OnCollectionChanged;
|
||||
_snapshot = new Collection<T>();
|
||||
}
|
||||
|
||||
public DependencyObject AssociatedObject
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedObject;
|
||||
}
|
||||
}
|
||||
|
||||
DependencyObject IAttachedObject.AssociatedObject => AssociatedObject;
|
||||
|
||||
public void Attach(DependencyObject dependencyObject)
|
||||
{
|
||||
if (Equals(dependencyObject, AssociatedObject))
|
||||
return;
|
||||
if (AssociatedObject != null)
|
||||
throw new InvalidOperationException();
|
||||
if (Interaction.ShouldRunInDesignMode || !(bool) GetValue(DesignerProperties.IsInDesignModeProperty))
|
||||
{
|
||||
WritePreamble();
|
||||
_associatedObject = dependencyObject;
|
||||
WritePostscript();
|
||||
}
|
||||
OnAttached();
|
||||
}
|
||||
|
||||
public void Detach()
|
||||
{
|
||||
OnDetaching();
|
||||
WritePreamble();
|
||||
_associatedObject = null;
|
||||
WritePostscript();
|
||||
}
|
||||
|
||||
protected abstract void OnAttached();
|
||||
|
||||
protected abstract void OnDetaching();
|
||||
|
||||
internal abstract void ItemAdded(T item);
|
||||
|
||||
internal abstract void ItemRemoved(T item);
|
||||
|
||||
private void VerifyAdd(T item)
|
||||
{
|
||||
if (item == null) throw new ArgumentNullException(nameof(item));
|
||||
if (_snapshot.Contains(item))
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.DuplicateItemInCollectionExceptionMessage, new object[]
|
||||
{
|
||||
typeof(T).Name,
|
||||
GetType().Name
|
||||
}));
|
||||
}
|
||||
|
||||
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
var enumerator1 = e.NewItems.GetEnumerator();
|
||||
try
|
||||
{
|
||||
while (enumerator1.MoveNext())
|
||||
{
|
||||
var current = (T) enumerator1.Current;
|
||||
try
|
||||
{
|
||||
VerifyAdd(current);
|
||||
ItemAdded(current);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_snapshot.Insert(IndexOf(current), current);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (enumerator1 is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
var enumerator2 = e.OldItems.GetEnumerator();
|
||||
try
|
||||
{
|
||||
while (enumerator2.MoveNext())
|
||||
{
|
||||
var current = (T) enumerator2.Current;
|
||||
ItemRemoved(current);
|
||||
_snapshot.Remove(current);
|
||||
}
|
||||
break;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (enumerator2 is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
foreach (T oldItem in e.OldItems)
|
||||
{
|
||||
ItemRemoved(oldItem);
|
||||
_snapshot.Remove(oldItem);
|
||||
}
|
||||
var enumerator3 = e.NewItems.GetEnumerator();
|
||||
try
|
||||
{
|
||||
while (enumerator3.MoveNext())
|
||||
{
|
||||
var current = (T) enumerator3.Current;
|
||||
try
|
||||
{
|
||||
VerifyAdd(current);
|
||||
ItemAdded(current);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_snapshot.Insert(IndexOf(current), current);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (enumerator3 is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
foreach (var obj in _snapshot)
|
||||
ItemRemoved(obj);
|
||||
_snapshot = new Collection<T>();
|
||||
using (var enumerator4 = GetEnumerator())
|
||||
{
|
||||
while (enumerator4.MoveNext())
|
||||
{
|
||||
var current = enumerator4.Current;
|
||||
VerifyAdd(current);
|
||||
ItemAdded(current);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public abstract class Behavior<T> : Behavior where T : DependencyObject
|
||||
{
|
||||
protected Behavior() : base(typeof(T))
|
||||
{
|
||||
}
|
||||
|
||||
protected new T AssociatedObject => (T)base.AssociatedObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public abstract class Behavior : Animatable, IAttachedObject
|
||||
{
|
||||
private DependencyObject _associatedObject;
|
||||
private readonly Type _associatedType;
|
||||
|
||||
internal Behavior(Type associatedType)
|
||||
{
|
||||
_associatedType = associatedType;
|
||||
}
|
||||
|
||||
protected DependencyObject AssociatedObject
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedObject;
|
||||
}
|
||||
}
|
||||
|
||||
protected Type AssociatedType
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedType;
|
||||
}
|
||||
}
|
||||
|
||||
public void Attach(DependencyObject dependencyObject)
|
||||
{
|
||||
if (!Equals(dependencyObject, AssociatedObject))
|
||||
{
|
||||
if (AssociatedObject != null)
|
||||
throw new InvalidOperationException(ExceptionStringTable
|
||||
.CannotHostBehaviorMultipleTimesExceptionMessage);
|
||||
if (dependencyObject != null && !AssociatedType.IsInstanceOfType(dependencyObject))
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.TypeConstraintViolatedExceptionMessage,
|
||||
new object[] {GetType().Name, dependencyObject.GetType().Name, AssociatedType.Name}));
|
||||
WritePreamble();
|
||||
_associatedObject = dependencyObject;
|
||||
WritePostscript();
|
||||
OnAssociatedObjectChanged();
|
||||
OnAttached();
|
||||
}
|
||||
}
|
||||
|
||||
public void Detach()
|
||||
{
|
||||
OnDetaching();
|
||||
WritePreamble();
|
||||
_associatedObject = null;
|
||||
WritePostscript();
|
||||
OnAssociatedObjectChanged();
|
||||
}
|
||||
|
||||
DependencyObject IAttachedObject.AssociatedObject =>
|
||||
AssociatedObject;
|
||||
|
||||
internal event EventHandler AssociatedObjectChanged;
|
||||
|
||||
protected override Freezable CreateInstanceCore()
|
||||
{
|
||||
return (Freezable) Activator.CreateInstance(GetType());
|
||||
}
|
||||
|
||||
private void OnAssociatedObjectChanged()
|
||||
{
|
||||
AssociatedObjectChanged?.Invoke(this, new EventArgs());
|
||||
}
|
||||
|
||||
protected virtual void OnAttached()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnDetaching()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public sealed class BehaviorCollection : AttachableCollection<Behavior>
|
||||
{
|
||||
internal BehaviorCollection()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
foreach (var behavior in this)
|
||||
behavior.Attach(AssociatedObject);
|
||||
}
|
||||
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
foreach (var behavior in this)
|
||||
behavior.Detach();
|
||||
}
|
||||
|
||||
internal override void ItemAdded(Behavior item)
|
||||
{
|
||||
if (AssociatedObject == null)
|
||||
return;
|
||||
item.Attach(AssociatedObject);
|
||||
}
|
||||
|
||||
internal override void ItemRemoved(Behavior item)
|
||||
{
|
||||
if (((IAttachedObject) item).AssociatedObject == null)
|
||||
return;
|
||||
item.Detach();
|
||||
}
|
||||
|
||||
protected override Freezable CreateInstanceCore()
|
||||
{
|
||||
return new BehaviorCollection();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
/// <summary>
|
||||
/// 控件库使用的所有命令(为了统一,不使用wpf自带的命令)
|
||||
/// </summary>
|
||||
public static class ControlCommands
|
||||
{
|
||||
/// <summary>
|
||||
/// 搜索
|
||||
/// </summary>
|
||||
public static RoutedCommand Search { get; } = new RoutedCommand(nameof(Search), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 清除
|
||||
/// </summary>
|
||||
public static RoutedCommand Clear { get; } = new RoutedCommand(nameof(Clear), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 切换
|
||||
/// </summary>
|
||||
public static RoutedCommand Switch { get; } = new RoutedCommand(nameof(Switch), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 右转
|
||||
/// </summary>
|
||||
public static RoutedCommand RotateRight { get; } = new RoutedCommand(nameof(RotateRight), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 左转
|
||||
/// </summary>
|
||||
public static RoutedCommand RotateLeft { get; } = new RoutedCommand(nameof(RotateLeft), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 小
|
||||
/// </summary>
|
||||
public static RoutedCommand Reduce { get; } = new RoutedCommand(nameof(Reduce), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 大
|
||||
/// </summary>
|
||||
public static RoutedCommand Enlarge { get; } = new RoutedCommand(nameof(Enlarge), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 还原
|
||||
/// </summary>
|
||||
public static RoutedCommand Restore { get; } = new RoutedCommand(nameof(Restore), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 打开
|
||||
/// </summary>
|
||||
public static RoutedCommand Open { get; } = new RoutedCommand(nameof(Open), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 保存
|
||||
/// </summary>
|
||||
public static RoutedCommand Save { get; } = new RoutedCommand(nameof(Save), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 选中
|
||||
/// </summary>
|
||||
public static RoutedCommand Selected { get; } = new RoutedCommand(nameof(Selected), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 关闭
|
||||
/// </summary>
|
||||
public static RoutedCommand Close { get; } = new RoutedCommand(nameof(Close), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 取消
|
||||
/// </summary>
|
||||
public static RoutedCommand Cancel { get; } = new RoutedCommand(nameof(Cancel), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 确定
|
||||
/// </summary>
|
||||
public static RoutedCommand Confirm { get; } = new RoutedCommand(nameof(Confirm), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 是
|
||||
/// </summary>
|
||||
public static RoutedCommand Yes { get; } = new RoutedCommand(nameof(Yes), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 否
|
||||
/// </summary>
|
||||
public static RoutedCommand No { get; } = new RoutedCommand(nameof(No), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 关闭所有
|
||||
/// </summary>
|
||||
public static RoutedCommand CloseAll { get; } = new RoutedCommand(nameof(CloseAll), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 关闭其他
|
||||
/// </summary>
|
||||
public static RoutedCommand CloseOther { get; } = new RoutedCommand(nameof(CloseOther), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 上一个
|
||||
/// </summary>
|
||||
public static RoutedCommand Prev { get; } = new RoutedCommand(nameof(Prev), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 下一个
|
||||
/// </summary>
|
||||
public static RoutedCommand Next { get; } = new RoutedCommand(nameof(Next), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 上午
|
||||
/// </summary>
|
||||
public static RoutedCommand Am { get; } = new RoutedCommand(nameof(Am), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 下午
|
||||
/// </summary>
|
||||
public static RoutedCommand Pm { get; } = new RoutedCommand(nameof(Pm), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 确认
|
||||
/// </summary>
|
||||
public static RoutedCommand Sure { get; } = new RoutedCommand(nameof(Sure), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 小时改变
|
||||
/// </summary>
|
||||
public static RoutedCommand HourChange { get; } = new RoutedCommand(nameof(HourChange), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 分钟改变
|
||||
/// </summary>
|
||||
public static RoutedCommand MinuteChange { get; } = new RoutedCommand(nameof(MinuteChange), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 秒改变
|
||||
/// </summary>
|
||||
public static RoutedCommand SecondChange { get; } = new RoutedCommand(nameof(SecondChange), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标移动
|
||||
/// </summary>
|
||||
public static RoutedCommand MouseMove { get; } = new RoutedCommand(nameof(MouseMove), typeof(ControlCommands));
|
||||
|
||||
/// <summary>
|
||||
/// 打开链接
|
||||
/// </summary>
|
||||
public static RoutedCommand OpenLink { get; } = new RoutedCommand(nameof(OpenLink), typeof(ControlCommands));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = true)]
|
||||
public sealed class DefaultTriggerAttribute : Attribute
|
||||
{
|
||||
private readonly object[] _parameters;
|
||||
|
||||
public DefaultTriggerAttribute(Type targetType, Type triggerType, object parameter) : this(targetType,
|
||||
triggerType, new[] {parameter})
|
||||
{
|
||||
}
|
||||
|
||||
public DefaultTriggerAttribute(Type targetType, Type triggerType, params object[] parameters)
|
||||
{
|
||||
if (!typeof(TriggerBase).IsAssignableFrom(triggerType))
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.DefaultTriggerAttributeInvalidTriggerTypeSpecifiedExceptionMessage,
|
||||
new object[] {triggerType.Name}));
|
||||
TargetType = targetType;
|
||||
TriggerType = triggerType;
|
||||
_parameters = parameters;
|
||||
}
|
||||
|
||||
public IEnumerable Parameters =>
|
||||
_parameters;
|
||||
|
||||
public Type TargetType { get; }
|
||||
|
||||
public Type TriggerType { get; }
|
||||
|
||||
public TriggerBase Instantiate()
|
||||
{
|
||||
object obj2 = null;
|
||||
try
|
||||
{
|
||||
obj2 = Activator.CreateInstance(TriggerType, _parameters);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
return (TriggerBase) obj2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*refrence from https://github.com/lbugnion/mvvmlight*/
|
||||
|
||||
//The MIT License(MIT)
|
||||
|
||||
//Copyright(c) 2009 - 2018 Laurent Bugnion
|
||||
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
//of this software and associated documentation files (the "Software"), to deal
|
||||
//in the Software without restriction, including without limitation the rights
|
||||
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
//copies of the Software, and to permit persons to whom the Software is
|
||||
//furnished to do so, subject to the following conditions:
|
||||
|
||||
//The above copyright notice and this permission notice shall be included in all
|
||||
//copies or substantial portions of the Software.
|
||||
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public class EventToCommand : TriggerAction<DependencyObject>
|
||||
{
|
||||
public const string EventArgsConverterParameterPropertyName = "EventArgsConverterParameter";
|
||||
|
||||
public const string AlwaysInvokeCommandPropertyName = "AlwaysInvokeCommand";
|
||||
|
||||
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(
|
||||
nameof(CommandParameter), typeof(object), typeof(EventToCommand), new PropertyMetadata(null,
|
||||
(s, e) =>
|
||||
{
|
||||
var eventToCommand = s as EventToCommand;
|
||||
if (eventToCommand?.AssociatedObject == null)
|
||||
return;
|
||||
eventToCommand.EnableDisableElement();
|
||||
}));
|
||||
|
||||
public static readonly DependencyProperty CommandProperty =
|
||||
DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(EventToCommand),
|
||||
new PropertyMetadata(null, (s, e) => OnCommandChanged(s as EventToCommand, e)));
|
||||
|
||||
public static readonly DependencyProperty MustToggleIsEnabledProperty = DependencyProperty.Register(
|
||||
nameof(MustToggleIsEnabled), typeof(bool), typeof(EventToCommand), new PropertyMetadata(ValueBoxes.FalseBox,
|
||||
(s, e) =>
|
||||
{
|
||||
var eventToCommand = s as EventToCommand;
|
||||
if (eventToCommand?.AssociatedObject == null)
|
||||
return;
|
||||
eventToCommand.EnableDisableElement();
|
||||
}));
|
||||
|
||||
public static readonly DependencyProperty EventArgsConverterParameterProperty =
|
||||
DependencyProperty.Register(nameof(EventArgsConverterParameter), typeof(object),
|
||||
typeof(EventToCommand), new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty AlwaysInvokeCommandProperty =
|
||||
DependencyProperty.Register(nameof(AlwaysInvokeCommand), typeof(bool), typeof(EventToCommand),
|
||||
new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
private object _commandParameterValue;
|
||||
|
||||
private bool? _mustToggleValue;
|
||||
|
||||
public ICommand Command
|
||||
{
|
||||
get => (ICommand) GetValue(CommandProperty);
|
||||
set => SetValue(CommandProperty, value);
|
||||
}
|
||||
|
||||
public object CommandParameter
|
||||
{
|
||||
get => GetValue(CommandParameterProperty);
|
||||
set => SetValue(CommandParameterProperty, value);
|
||||
}
|
||||
|
||||
public object CommandParameterValue
|
||||
{
|
||||
get => _commandParameterValue ?? CommandParameter;
|
||||
set
|
||||
{
|
||||
_commandParameterValue = value;
|
||||
EnableDisableElement();
|
||||
}
|
||||
}
|
||||
|
||||
public bool MustToggleIsEnabled
|
||||
{
|
||||
get => (bool) GetValue(MustToggleIsEnabledProperty);
|
||||
set => SetValue(MustToggleIsEnabledProperty, value);
|
||||
}
|
||||
|
||||
public bool MustToggleIsEnabledValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_mustToggleValue.HasValue)
|
||||
return _mustToggleValue.Value;
|
||||
return MustToggleIsEnabled;
|
||||
}
|
||||
set
|
||||
{
|
||||
_mustToggleValue = value;
|
||||
EnableDisableElement();
|
||||
}
|
||||
}
|
||||
|
||||
public bool PassEventArgsToCommand { get; set; }
|
||||
|
||||
public IEventArgsConverter EventArgsConverter { get; set; }
|
||||
|
||||
public object EventArgsConverterParameter
|
||||
{
|
||||
get => GetValue(EventArgsConverterParameterProperty);
|
||||
set => SetValue(EventArgsConverterParameterProperty, value);
|
||||
}
|
||||
|
||||
public bool AlwaysInvokeCommand
|
||||
{
|
||||
get => (bool) GetValue(AlwaysInvokeCommandProperty);
|
||||
set => SetValue(AlwaysInvokeCommandProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
EnableDisableElement();
|
||||
}
|
||||
|
||||
private FrameworkElement GetAssociatedObject() => AssociatedObject as FrameworkElement;
|
||||
|
||||
private ICommand GetCommand() => Command;
|
||||
|
||||
public void Invoke() => Invoke(null);
|
||||
|
||||
protected override void Invoke(object parameter)
|
||||
{
|
||||
if (AssociatedElementIsDisabled() && !AlwaysInvokeCommand)
|
||||
return;
|
||||
var command = GetCommand();
|
||||
var parameter1 = CommandParameterValue;
|
||||
if (parameter1 == null && PassEventArgsToCommand)
|
||||
parameter1 = EventArgsConverter == null
|
||||
? parameter
|
||||
: EventArgsConverter.Convert(parameter, EventArgsConverterParameter);
|
||||
if (command == null || !command.CanExecute(parameter1))
|
||||
return;
|
||||
command.Execute(parameter1);
|
||||
}
|
||||
|
||||
private static void OnCommandChanged(EventToCommand element, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (element == null)
|
||||
return;
|
||||
if (e.OldValue != null)
|
||||
((ICommand) e.OldValue).CanExecuteChanged -= element.OnCommandCanExecuteChanged;
|
||||
var newValue = (ICommand) e.NewValue;
|
||||
if (newValue != null)
|
||||
newValue.CanExecuteChanged += element.OnCommandCanExecuteChanged;
|
||||
element.EnableDisableElement();
|
||||
}
|
||||
|
||||
private bool AssociatedElementIsDisabled()
|
||||
{
|
||||
var associatedObject = GetAssociatedObject();
|
||||
if (AssociatedObject == null)
|
||||
return true;
|
||||
if (associatedObject != null)
|
||||
return !associatedObject.IsEnabled;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void EnableDisableElement()
|
||||
{
|
||||
var associatedObject = GetAssociatedObject();
|
||||
if (associatedObject == null)
|
||||
return;
|
||||
var command = GetCommand();
|
||||
if (!MustToggleIsEnabledValue || command == null)
|
||||
return;
|
||||
associatedObject.IsEnabled = command.CanExecute(CommandParameterValue);
|
||||
}
|
||||
|
||||
private void OnCommandCanExecuteChanged(object sender, EventArgs e) => EnableDisableElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public class EventTrigger : EventTriggerBase<object>
|
||||
{
|
||||
public static readonly DependencyProperty EventNameProperty = DependencyProperty.Register("EventName", typeof(string), typeof(EventTrigger), new FrameworkPropertyMetadata("Loaded", OnEventNameChanged));
|
||||
|
||||
public EventTrigger()
|
||||
{
|
||||
}
|
||||
|
||||
public EventTrigger(string eventName) => EventName = eventName;
|
||||
|
||||
protected override string GetEventName() => EventName;
|
||||
|
||||
private static void OnEventNameChanged(object sender, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
((EventTrigger)sender).OnEventNameChanged((string)args.OldValue, (string)args.NewValue);
|
||||
}
|
||||
|
||||
public string EventName
|
||||
{
|
||||
get => (string)GetValue(EventNameProperty);
|
||||
|
||||
set => SetValue(EventNameProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public abstract class EventTriggerBase<T> : EventTriggerBase where T : class
|
||||
{
|
||||
protected EventTriggerBase() : base(typeof(T))
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnSourceChanged(T oldSource, T newSource)
|
||||
{
|
||||
}
|
||||
|
||||
internal sealed override void OnSourceChangedImpl(object oldSource, object newSource)
|
||||
{
|
||||
base.OnSourceChangedImpl(oldSource, newSource);
|
||||
OnSourceChanged(oldSource as T, newSource as T);
|
||||
}
|
||||
|
||||
public new T Source => (T)base.Source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public abstract class EventTriggerBase : TriggerBase
|
||||
{
|
||||
public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register("SourceName",
|
||||
typeof(string), typeof(EventTriggerBase), new PropertyMetadata(OnSourceNameChanged));
|
||||
|
||||
public static readonly DependencyProperty SourceObjectProperty = DependencyProperty.Register("SourceObject",
|
||||
typeof(object), typeof(EventTriggerBase), new PropertyMetadata(OnSourceObjectChanged));
|
||||
|
||||
private MethodInfo _eventHandlerMethodInfo;
|
||||
|
||||
internal EventTriggerBase(Type sourceTypeConstraint) : base(typeof(DependencyObject))
|
||||
{
|
||||
SourceTypeConstraint = sourceTypeConstraint;
|
||||
SourceNameResolver = new NameResolver();
|
||||
RegisterSourceChanged();
|
||||
}
|
||||
|
||||
protected sealed override Type AssociatedObjectTypeConstraint
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TypeDescriptor.GetAttributes(GetType())[typeof(TypeConstraintAttribute)] is TypeConstraintAttribute attribute)
|
||||
return attribute.Constraint;
|
||||
return typeof(DependencyObject);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsLoadedRegistered { get; set; }
|
||||
|
||||
private bool IsSourceChangedRegistered { get; set; }
|
||||
|
||||
private bool IsSourceNameSet
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(SourceName))
|
||||
return ReadLocalValue(SourceNameProperty) != DependencyProperty.UnsetValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public object Source
|
||||
{
|
||||
get
|
||||
{
|
||||
object associatedObject = AssociatedObject;
|
||||
if (SourceObject != null)
|
||||
return SourceObject;
|
||||
if (IsSourceNameSet)
|
||||
{
|
||||
associatedObject = SourceNameResolver.Object;
|
||||
if (associatedObject != null && !SourceTypeConstraint.IsInstanceOfType(associatedObject))
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.RetargetedTypeConstraintViolatedExceptionMessage, GetType().Name,
|
||||
associatedObject.GetType(), SourceTypeConstraint, "Source"));
|
||||
}
|
||||
return associatedObject;
|
||||
}
|
||||
}
|
||||
|
||||
public string SourceName
|
||||
{
|
||||
get =>
|
||||
(string) GetValue(SourceNameProperty);
|
||||
set => SetValue(SourceNameProperty, value);
|
||||
}
|
||||
|
||||
private NameResolver SourceNameResolver { get; }
|
||||
|
||||
public object SourceObject
|
||||
{
|
||||
get =>
|
||||
GetValue(SourceObjectProperty);
|
||||
set => SetValue(SourceObjectProperty, value);
|
||||
}
|
||||
|
||||
protected Type SourceTypeConstraint { get; }
|
||||
|
||||
protected abstract string GetEventName();
|
||||
|
||||
private static bool IsValidEvent(EventInfo eventInfo)
|
||||
{
|
||||
var eventHandlerType = eventInfo.EventHandlerType;
|
||||
if (!typeof(Delegate).IsAssignableFrom(eventInfo.EventHandlerType))
|
||||
return false;
|
||||
var parameters = eventHandlerType.GetMethod("Invoke")?.GetParameters();
|
||||
return parameters != null && parameters.Length == 2 && typeof(object).IsAssignableFrom(parameters[0].ParameterType) && typeof(EventArgs).IsAssignableFrom(parameters[1].ParameterType);
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
var associatedObject = AssociatedObject;
|
||||
var behavior = associatedObject as Behavior;
|
||||
var element = associatedObject as FrameworkElement;
|
||||
RegisterSourceChanged();
|
||||
if (behavior != null)
|
||||
{
|
||||
behavior.AssociatedObjectChanged += OnBehaviorHostChanged;
|
||||
}
|
||||
else if (SourceObject != null || element == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnSourceChanged(null, Source);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SourceNameResolver.NameScopeReferenceElement = element;
|
||||
}
|
||||
if (string.Compare(GetEventName(), "Loaded", StringComparison.Ordinal) == 0 && element != null &&
|
||||
!Interaction.IsElementLoaded(element))
|
||||
RegisterLoaded(element);
|
||||
}
|
||||
|
||||
private void OnBehaviorHostChanged(object sender, EventArgs e)
|
||||
{
|
||||
SourceNameResolver.NameScopeReferenceElement =
|
||||
((IAttachedObject) sender).AssociatedObject as FrameworkElement;
|
||||
}
|
||||
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
base.OnDetaching();
|
||||
var associatedObject = AssociatedObject as Behavior;
|
||||
var associatedElement = AssociatedObject as FrameworkElement;
|
||||
try
|
||||
{
|
||||
OnSourceChanged(Source, null);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
UnregisterSourceChanged();
|
||||
if (associatedObject != null)
|
||||
associatedObject.AssociatedObjectChanged -= OnBehaviorHostChanged;
|
||||
SourceNameResolver.NameScopeReferenceElement = null;
|
||||
if (string.Compare(GetEventName(), "Loaded", StringComparison.Ordinal) == 0 && associatedElement != null)
|
||||
UnregisterLoaded(associatedElement);
|
||||
}
|
||||
|
||||
protected virtual void OnEvent(EventArgs eventArgs)
|
||||
{
|
||||
InvokeActions(eventArgs);
|
||||
}
|
||||
|
||||
private void OnEventImpl(object sender, EventArgs eventArgs)
|
||||
{
|
||||
OnEvent(eventArgs);
|
||||
}
|
||||
|
||||
internal void OnEventNameChanged(string oldEventName, string newEventName)
|
||||
{
|
||||
if (AssociatedObject != null)
|
||||
{
|
||||
var source = Source as FrameworkElement;
|
||||
if (source != null && string.Compare(oldEventName, "Loaded", StringComparison.Ordinal) == 0)
|
||||
UnregisterLoaded(source);
|
||||
else if (!string.IsNullOrEmpty(oldEventName))
|
||||
UnregisterEvent(Source, oldEventName);
|
||||
if (source != null && string.Compare(newEventName, "Loaded", StringComparison.Ordinal) == 0)
|
||||
RegisterLoaded(source);
|
||||
else if (!string.IsNullOrEmpty(newEventName))
|
||||
RegisterEvent(Source, newEventName);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSourceChanged(object oldSource, object newSource)
|
||||
{
|
||||
if (AssociatedObject != null)
|
||||
OnSourceChangedImpl(oldSource, newSource);
|
||||
}
|
||||
|
||||
internal virtual void OnSourceChangedImpl(object oldSource, object newSource)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(GetEventName()) &&
|
||||
string.Compare(GetEventName(), "Loaded", StringComparison.Ordinal) != 0)
|
||||
{
|
||||
if (oldSource != null && SourceTypeConstraint.IsInstanceOfType(oldSource))
|
||||
UnregisterEvent(oldSource, GetEventName());
|
||||
if (newSource != null && SourceTypeConstraint.IsInstanceOfType(newSource))
|
||||
RegisterEvent(newSource, GetEventName());
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnSourceNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
var base2 = (EventTriggerBase) obj;
|
||||
base2.SourceNameResolver.Name = (string) args.NewValue;
|
||||
}
|
||||
|
||||
private void OnSourceNameResolverElementChanged(object sender, NameResolvedEventArgs e)
|
||||
{
|
||||
if (SourceObject == null)
|
||||
OnSourceChanged(e.OldObject, e.NewObject);
|
||||
}
|
||||
|
||||
private static void OnSourceObjectChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
var base2 = (EventTriggerBase) obj;
|
||||
object newSource = base2.SourceNameResolver.Object;
|
||||
if (args.NewValue == null)
|
||||
{
|
||||
base2.OnSourceChanged(args.OldValue, newSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (args.OldValue == null && newSource != null)
|
||||
base2.UnregisterEvent(newSource, base2.GetEventName());
|
||||
base2.OnSourceChanged(args.OldValue, args.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterEvent(object obj, string eventName)
|
||||
{
|
||||
var eventInfo = obj.GetType().GetEvent(eventName);
|
||||
if (eventInfo == null)
|
||||
{
|
||||
if (SourceObject != null)
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.EventTriggerCannotFindEventNameExceptionMessage,
|
||||
new object[] {eventName, obj.GetType().Name}));
|
||||
}
|
||||
else if (!IsValidEvent(eventInfo))
|
||||
{
|
||||
if (SourceObject != null)
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.EventTriggerBaseInvalidEventExceptionMessage,
|
||||
new object[] {eventName, obj.GetType().Name}));
|
||||
}
|
||||
else
|
||||
{
|
||||
_eventHandlerMethodInfo =
|
||||
typeof(EventTriggerBase).GetMethod("OnEventImpl", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
eventInfo.AddEventHandler(obj,
|
||||
Delegate.CreateDelegate(eventInfo.EventHandlerType, this, _eventHandlerMethodInfo ?? throw new InvalidOperationException()));
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterLoaded(FrameworkElement associatedElement)
|
||||
{
|
||||
if (!IsLoadedRegistered && associatedElement != null)
|
||||
{
|
||||
associatedElement.Loaded += OnEventImpl;
|
||||
IsLoadedRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterSourceChanged()
|
||||
{
|
||||
if (!IsSourceChangedRegistered)
|
||||
{
|
||||
SourceNameResolver.ResolvedElementChanged += OnSourceNameResolverElementChanged;
|
||||
IsSourceChangedRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterEvent(object obj, string eventName)
|
||||
{
|
||||
if (string.Compare(eventName, "Loaded", StringComparison.Ordinal) == 0)
|
||||
{
|
||||
if (obj is FrameworkElement associatedElement)
|
||||
UnregisterLoaded(associatedElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnregisterEventImpl(obj, eventName);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterEventImpl(object obj, string eventName)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
if (_eventHandlerMethodInfo != null)
|
||||
{
|
||||
var info = type.GetEvent(eventName);
|
||||
info.RemoveEventHandler(obj,
|
||||
Delegate.CreateDelegate(info.EventHandlerType, this, _eventHandlerMethodInfo));
|
||||
_eventHandlerMethodInfo = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterLoaded(FrameworkElement associatedElement)
|
||||
{
|
||||
if (IsLoadedRegistered && associatedElement != null)
|
||||
{
|
||||
associatedElement.Loaded -= OnEventImpl;
|
||||
IsLoadedRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterSourceChanged()
|
||||
{
|
||||
if (IsSourceChangedRegistered)
|
||||
{
|
||||
SourceNameResolver.ResolvedElementChanged -= OnSourceNameResolverElementChanged;
|
||||
IsSourceChangedRegistered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Resources;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
internal class ExceptionStringTable
|
||||
{
|
||||
private static ResourceManager ResourceMan;
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||
internal static ResourceManager ResourceManager => ResourceMan ?? (ResourceMan = new ResourceManager(nameof(ExceptionStringTable),
|
||||
typeof(ExceptionStringTable).Assembly));
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||
internal static CultureInfo Culture { get; set; }
|
||||
|
||||
internal static string CannotHostBehaviorCollectionMultipleTimesExceptionMessage =>
|
||||
ResourceManager.GetString(nameof(CannotHostBehaviorCollectionMultipleTimesExceptionMessage), Culture);
|
||||
|
||||
internal static string CannotHostBehaviorMultipleTimesExceptionMessage =>
|
||||
ResourceManager.GetString(nameof(CannotHostBehaviorMultipleTimesExceptionMessage), Culture);
|
||||
|
||||
internal static string CannotHostTriggerActionMultipleTimesExceptionMessage =>
|
||||
ResourceManager.GetString(nameof(CannotHostTriggerActionMultipleTimesExceptionMessage), Culture);
|
||||
|
||||
internal static string CannotHostTriggerCollectionMultipleTimesExceptionMessage =>
|
||||
ResourceManager.GetString(nameof(CannotHostTriggerCollectionMultipleTimesExceptionMessage), Culture);
|
||||
|
||||
internal static string CannotHostTriggerMultipleTimesExceptionMessage =>
|
||||
ResourceManager.GetString(nameof(CannotHostTriggerMultipleTimesExceptionMessage), Culture);
|
||||
|
||||
internal static string CommandDoesNotExistOnBehaviorWarningMessage =>
|
||||
ResourceManager.GetString(nameof(CommandDoesNotExistOnBehaviorWarningMessage), Culture);
|
||||
|
||||
internal static string DefaultTriggerAttributeInvalidTriggerTypeSpecifiedExceptionMessage =>
|
||||
ResourceManager.GetString(nameof(DefaultTriggerAttributeInvalidTriggerTypeSpecifiedExceptionMessage),
|
||||
Culture);
|
||||
|
||||
internal static string DuplicateItemInCollectionExceptionMessage =>
|
||||
ResourceManager.GetString(nameof(DuplicateItemInCollectionExceptionMessage), Culture);
|
||||
|
||||
internal static string EventTriggerBaseInvalidEventExceptionMessage =>
|
||||
ResourceManager.GetString(nameof(EventTriggerBaseInvalidEventExceptionMessage), Culture);
|
||||
|
||||
internal static string EventTriggerCannotFindEventNameExceptionMessage =>
|
||||
ResourceManager.GetString(nameof(EventTriggerCannotFindEventNameExceptionMessage), Culture);
|
||||
|
||||
internal static string RetargetedTypeConstraintViolatedExceptionMessage =>
|
||||
ResourceManager.GetString(nameof(RetargetedTypeConstraintViolatedExceptionMessage), Culture);
|
||||
|
||||
internal static string TypeConstraintViolatedExceptionMessage =>
|
||||
ResourceManager.GetString(nameof(TypeConstraintViolatedExceptionMessage), Culture);
|
||||
|
||||
internal static string UnableToResolveTargetNameWarningMessage =>
|
||||
ResourceManager.GetString(nameof(UnableToResolveTargetNameWarningMessage), Culture);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public class ExtendedVisualStateManager
|
||||
{
|
||||
internal static Rect GetLayoutRect(FrameworkElement element)
|
||||
{
|
||||
var actualWidth = element.ActualWidth;
|
||||
var actualHeight = element.ActualHeight;
|
||||
if (element is Image || element is MediaElement)
|
||||
if (element.Parent is Canvas)
|
||||
{
|
||||
actualWidth = double.IsNaN(element.Width) ? actualWidth : element.Width;
|
||||
actualHeight = double.IsNaN(element.Height) ? actualHeight : element.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
actualWidth = element.RenderSize.Width;
|
||||
actualHeight = element.RenderSize.Height;
|
||||
}
|
||||
actualWidth = element.Visibility == Visibility.Collapsed ? 0.0 : actualWidth;
|
||||
actualHeight = element.Visibility == Visibility.Collapsed ? 0.0 : actualHeight;
|
||||
var margin = element.Margin;
|
||||
var layoutSlot = LayoutInformation.GetLayoutSlot(element);
|
||||
var x = 0.0;
|
||||
var y = 0.0;
|
||||
switch (element.HorizontalAlignment)
|
||||
{
|
||||
case HorizontalAlignment.Left:
|
||||
x = layoutSlot.Left + margin.Left;
|
||||
break;
|
||||
|
||||
case HorizontalAlignment.Center:
|
||||
x = (layoutSlot.Left + margin.Left + layoutSlot.Right - margin.Right) / 2.0 - actualWidth / 2.0;
|
||||
break;
|
||||
|
||||
case HorizontalAlignment.Right:
|
||||
x = layoutSlot.Right - margin.Right - actualWidth;
|
||||
break;
|
||||
|
||||
case HorizontalAlignment.Stretch:
|
||||
x = Math.Max(layoutSlot.Left + margin.Left,
|
||||
(layoutSlot.Left + margin.Left + layoutSlot.Right - margin.Right) / 2.0 - actualWidth / 2.0);
|
||||
break;
|
||||
}
|
||||
switch (element.VerticalAlignment)
|
||||
{
|
||||
case VerticalAlignment.Top:
|
||||
y = layoutSlot.Top + margin.Top;
|
||||
break;
|
||||
|
||||
case VerticalAlignment.Center:
|
||||
y = (layoutSlot.Top + margin.Top + layoutSlot.Bottom - margin.Bottom) / 2.0 - actualHeight / 2.0;
|
||||
break;
|
||||
|
||||
case VerticalAlignment.Bottom:
|
||||
y = layoutSlot.Bottom - margin.Bottom - actualHeight;
|
||||
break;
|
||||
|
||||
case VerticalAlignment.Stretch:
|
||||
y = Math.Max(layoutSlot.Top + margin.Top,
|
||||
(layoutSlot.Top + margin.Top + layoutSlot.Bottom - margin.Bottom) / 2.0 - actualHeight / 2.0);
|
||||
break;
|
||||
}
|
||||
return new Rect(x, y, actualWidth, actualHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Shapes;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public sealed class FluidMoveBehavior : FluidMoveBehaviorBase
|
||||
{
|
||||
private static readonly DependencyProperty CacheDuringOverlayProperty =
|
||||
DependencyProperty.RegisterAttached("CacheDuringOverlay", typeof(object),
|
||||
typeof(FluidMoveBehavior), new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty DurationProperty = DependencyProperty.Register("Duration",
|
||||
typeof(Duration), typeof(FluidMoveBehavior),
|
||||
new PropertyMetadata(new Duration(TimeSpan.FromSeconds(1.0))));
|
||||
|
||||
public static readonly DependencyProperty FloatAboveProperty =
|
||||
DependencyProperty.Register("FloatAbove", typeof(bool), typeof(FluidMoveBehavior),
|
||||
new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
private static readonly DependencyProperty HasTransformWrapperProperty =
|
||||
DependencyProperty.RegisterAttached("HasTransformWrapper", typeof(bool),
|
||||
typeof(FluidMoveBehavior), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
private static readonly DependencyProperty InitialIdentityTagProperty =
|
||||
DependencyProperty.RegisterAttached("InitialIdentityTag", typeof(object),
|
||||
typeof(FluidMoveBehavior), new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty InitialTagPathProperty =
|
||||
DependencyProperty.Register("InitialTagPath", typeof(string), typeof(FluidMoveBehavior),
|
||||
new PropertyMetadata(string.Empty));
|
||||
|
||||
public static readonly DependencyProperty InitialTagProperty =
|
||||
DependencyProperty.Register("InitialTag", typeof(TagType), typeof(FluidMoveBehavior),
|
||||
new PropertyMetadata(TagType.Element));
|
||||
|
||||
private static readonly DependencyProperty OverlayProperty =
|
||||
DependencyProperty.RegisterAttached("Overlay", typeof(object), typeof(FluidMoveBehavior),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
private static readonly Dictionary<object, Storyboard> TransitionStoryboardDictionary =
|
||||
new Dictionary<object, Storyboard>();
|
||||
|
||||
public Duration Duration
|
||||
{
|
||||
get =>
|
||||
(Duration) GetValue(DurationProperty);
|
||||
set => SetValue(DurationProperty, value);
|
||||
}
|
||||
|
||||
public bool FloatAbove
|
||||
{
|
||||
get =>
|
||||
(bool) GetValue(FloatAboveProperty);
|
||||
set => SetValue(FloatAboveProperty, value);
|
||||
}
|
||||
|
||||
public TagType InitialTag
|
||||
{
|
||||
get =>
|
||||
(TagType) GetValue(InitialTagProperty);
|
||||
set => SetValue(InitialTagProperty, value);
|
||||
}
|
||||
|
||||
public string InitialTagPath
|
||||
{
|
||||
get =>
|
||||
(string) GetValue(InitialTagPathProperty);
|
||||
set => SetValue(InitialTagPathProperty, value);
|
||||
}
|
||||
|
||||
protected override bool ShouldSkipInitialLayout
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!base.ShouldSkipInitialLayout) return InitialTag == TagType.DataContext;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddTransform(FrameworkElement child, Transform transform)
|
||||
{
|
||||
if (!(child.RenderTransform is TransformGroup renderTransform))
|
||||
{
|
||||
renderTransform = new TransformGroup
|
||||
{
|
||||
Children = {child.RenderTransform}
|
||||
};
|
||||
child.RenderTransform = renderTransform;
|
||||
SetHasTransformWrapper(child, true);
|
||||
}
|
||||
|
||||
renderTransform.Children.Add(transform);
|
||||
}
|
||||
|
||||
private Storyboard CreateTransitionStoryboard(FrameworkElement child, bool usingBeforeLoaded,
|
||||
ref Rect layoutRect, ref Rect currentRect)
|
||||
{
|
||||
var duration = Duration;
|
||||
var storyboard = new Storyboard
|
||||
{
|
||||
Duration = duration
|
||||
};
|
||||
var num = !usingBeforeLoaded || Math.Abs(layoutRect.Width) < 0.001
|
||||
? 1.0
|
||||
: currentRect.Width / layoutRect.Width;
|
||||
var num2 = !usingBeforeLoaded || Math.Abs(layoutRect.Height) < 0.001
|
||||
? 1.0
|
||||
: currentRect.Height / layoutRect.Height;
|
||||
var num3 = currentRect.Left - layoutRect.Left;
|
||||
var num4 = currentRect.Top - layoutRect.Top;
|
||||
var group = new TransformGroup();
|
||||
var transform = new ScaleTransform
|
||||
{
|
||||
ScaleX = num,
|
||||
ScaleY = num2
|
||||
};
|
||||
group.Children.Add(transform);
|
||||
var transform2 = new TranslateTransform
|
||||
{
|
||||
X = num3,
|
||||
Y = num4
|
||||
};
|
||||
group.Children.Add(transform2);
|
||||
AddTransform(child, group);
|
||||
var str = "(FrameworkElement.RenderTransform).";
|
||||
if (child.RenderTransform is TransformGroup renderTransform && GetHasTransformWrapper(child))
|
||||
{
|
||||
object obj2 = str;
|
||||
str = string.Concat(obj2, "(TransformGroup.Children)[", renderTransform.Children.Count - 1,
|
||||
"].");
|
||||
}
|
||||
|
||||
if (usingBeforeLoaded)
|
||||
{
|
||||
if (Math.Abs(num - 1.0) > 0.001)
|
||||
{
|
||||
var element = new DoubleAnimation
|
||||
{
|
||||
Duration = duration,
|
||||
From = num,
|
||||
To = 1.0
|
||||
};
|
||||
Storyboard.SetTarget(element, child);
|
||||
Storyboard.SetTargetProperty(element,
|
||||
new PropertyPath(str + "(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"));
|
||||
storyboard.Children.Add(element);
|
||||
}
|
||||
|
||||
if (Math.Abs(num2 - 1.0) > 0.001)
|
||||
{
|
||||
var animation3 = new DoubleAnimation
|
||||
{
|
||||
Duration = duration,
|
||||
From = num2,
|
||||
To = 1.0
|
||||
};
|
||||
Storyboard.SetTarget(animation3, child);
|
||||
Storyboard.SetTargetProperty(animation3,
|
||||
new PropertyPath(str + "(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"));
|
||||
storyboard.Children.Add(animation3);
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.Abs(num3) > 0.001)
|
||||
{
|
||||
var animation5 = new DoubleAnimation
|
||||
{
|
||||
Duration = duration,
|
||||
From = num3,
|
||||
To = 0.0
|
||||
};
|
||||
Storyboard.SetTarget(animation5, child);
|
||||
Storyboard.SetTargetProperty(animation5,
|
||||
new PropertyPath(str + "(TransformGroup.Children)[1].(TranslateTransform.X)"));
|
||||
storyboard.Children.Add(animation5);
|
||||
}
|
||||
|
||||
if (Math.Abs(num4) > 0.001)
|
||||
{
|
||||
var animation7 = new DoubleAnimation
|
||||
{
|
||||
Duration = duration,
|
||||
From = num4,
|
||||
To = 0.0
|
||||
};
|
||||
Storyboard.SetTarget(animation7, child);
|
||||
Storyboard.SetTargetProperty(animation7,
|
||||
new PropertyPath(str + "(TransformGroup.Children)[1].(TranslateTransform.Y)"));
|
||||
storyboard.Children.Add(animation7);
|
||||
}
|
||||
|
||||
return storyboard;
|
||||
}
|
||||
|
||||
protected override void EnsureTags(FrameworkElement child)
|
||||
{
|
||||
base.EnsureTags(child);
|
||||
if (InitialTag == TagType.DataContext &&
|
||||
!(child.ReadLocalValue(InitialIdentityTagProperty) is BindingExpression))
|
||||
child.SetBinding(InitialIdentityTagProperty, new Binding(InitialTagPath));
|
||||
}
|
||||
|
||||
private static bool GetHasTransformWrapper(DependencyObject obj)
|
||||
{
|
||||
return (bool) obj.GetValue(HasTransformWrapperProperty);
|
||||
}
|
||||
|
||||
private static object GetInitialIdentityTag(DependencyObject obj)
|
||||
{
|
||||
return obj.GetValue(InitialIdentityTagProperty);
|
||||
}
|
||||
|
||||
private static object GetOverlay(DependencyObject obj)
|
||||
{
|
||||
return obj.GetValue(OverlayProperty);
|
||||
}
|
||||
|
||||
private static Transform GetTransform(FrameworkElement child)
|
||||
{
|
||||
if (child.RenderTransform is TransformGroup renderTransform && renderTransform.Children.Count > 0)
|
||||
return renderTransform.Children[renderTransform.Children.Count - 1];
|
||||
return new TranslateTransform();
|
||||
}
|
||||
|
||||
private static bool IsClose(double a, double b)
|
||||
{
|
||||
return Math.Abs(a - b) < 1E-07;
|
||||
}
|
||||
|
||||
private static bool IsEmptyRect(Rect rect)
|
||||
{
|
||||
if (!rect.IsEmpty && !double.IsNaN(rect.Left)) return double.IsNaN(rect.Top);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void RemoveTransform(FrameworkElement child)
|
||||
{
|
||||
if (child.RenderTransform is TransformGroup renderTransform)
|
||||
{
|
||||
if (GetHasTransformWrapper(child))
|
||||
{
|
||||
child.RenderTransform = renderTransform.Children[0];
|
||||
SetHasTransformWrapper(child, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderTransform.Children.RemoveAt(renderTransform.Children.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetHasTransformWrapper(DependencyObject obj, bool value)
|
||||
{
|
||||
obj.SetValue(HasTransformWrapperProperty, value);
|
||||
}
|
||||
|
||||
private static void SetOverlay(DependencyObject obj, object value)
|
||||
{
|
||||
obj.SetValue(OverlayProperty, value);
|
||||
}
|
||||
|
||||
private static void TransferLocalValue(FrameworkElement element, DependencyProperty source,
|
||||
DependencyProperty dest)
|
||||
{
|
||||
var obj2 = element.ReadLocalValue(source);
|
||||
if (obj2 is BindingExpressionBase base2)
|
||||
element.SetBinding(dest, base2.ParentBindingBase);
|
||||
else if (obj2 == DependencyProperty.UnsetValue)
|
||||
element.ClearValue(dest);
|
||||
else
|
||||
element.SetValue(dest, element.GetAnimationBaseValue(source));
|
||||
element.ClearValue(source);
|
||||
}
|
||||
|
||||
internal override void UpdateLayoutTransitionCore(FrameworkElement child, FrameworkElement root,
|
||||
object tag, TagData newTagData)
|
||||
{
|
||||
Rect empty;
|
||||
var flag = false;
|
||||
var usingBeforeLoaded = false;
|
||||
var initialIdentityTag = GetInitialIdentityTag(child);
|
||||
var flag3 = TagDictionary.TryGetValue(tag, out var data);
|
||||
if (flag3 && data.InitialTag != initialIdentityTag)
|
||||
{
|
||||
flag3 = false;
|
||||
TagDictionary.Remove(tag);
|
||||
}
|
||||
|
||||
if (!flag3)
|
||||
{
|
||||
if (initialIdentityTag != null && TagDictionary.TryGetValue(initialIdentityTag, out var data2))
|
||||
{
|
||||
empty = TranslateRect(data2.AppRect, root, newTagData.Parent);
|
||||
flag = true;
|
||||
usingBeforeLoaded = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
empty = Rect.Empty;
|
||||
}
|
||||
|
||||
data = new TagData
|
||||
{
|
||||
ParentRect = Rect.Empty,
|
||||
AppRect = Rect.Empty,
|
||||
Parent = newTagData.Parent,
|
||||
Child = child,
|
||||
Timestamp = DateTime.Now,
|
||||
InitialTag = initialIdentityTag
|
||||
};
|
||||
TagDictionary.Add(tag, data);
|
||||
}
|
||||
else if (!Equals(data.Parent, VisualTreeHelper.GetParent(child)))
|
||||
{
|
||||
empty = TranslateRect(data.AppRect, root, newTagData.Parent);
|
||||
flag = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
empty = data.ParentRect;
|
||||
}
|
||||
|
||||
var originalChild = child;
|
||||
if (!IsEmptyRect(empty) && !IsEmptyRect(newTagData.ParentRect) &&
|
||||
(!IsClose(empty.Left, newTagData.ParentRect.Left) ||
|
||||
!IsClose(empty.Top, newTagData.ParentRect.Top)) || !Equals(child, data.Child) &&
|
||||
TransitionStoryboardDictionary.ContainsKey(tag))
|
||||
{
|
||||
var rect = empty;
|
||||
var flag4 = false;
|
||||
if (TransitionStoryboardDictionary.TryGetValue(tag, out var storyboard))
|
||||
{
|
||||
var obj3 = GetOverlay(data.Child);
|
||||
var adorner = (AdornerContainer) obj3;
|
||||
flag4 = obj3 != null;
|
||||
var element = data.Child;
|
||||
if (obj3 != null)
|
||||
{
|
||||
if (adorner.Child is Canvas canvas) element = canvas.Children[0] as FrameworkElement;
|
||||
}
|
||||
|
||||
if (!usingBeforeLoaded) rect = GetTransform(element).TransformBounds(rect);
|
||||
TransitionStoryboardDictionary.Remove(tag);
|
||||
storyboard.Stop();
|
||||
RemoveTransform(element);
|
||||
if (obj3 != null)
|
||||
{
|
||||
AdornerLayer.GetAdornerLayer(root).Remove(adorner);
|
||||
TransferLocalValue(data.Child, CacheDuringOverlayProperty,
|
||||
UIElement.RenderTransformProperty);
|
||||
SetOverlay(data.Child, null);
|
||||
}
|
||||
}
|
||||
|
||||
object overlay = null;
|
||||
if (flag4 || flag && FloatAbove)
|
||||
{
|
||||
var canvas2 = new Canvas
|
||||
{
|
||||
Width = newTagData.ParentRect.Width,
|
||||
Height = newTagData.ParentRect.Height,
|
||||
IsHitTestVisible = false
|
||||
};
|
||||
var rectangle = new Rectangle
|
||||
{
|
||||
Width = newTagData.ParentRect.Width,
|
||||
Height = newTagData.ParentRect.Height,
|
||||
IsHitTestVisible = false,
|
||||
Fill = new VisualBrush(child)
|
||||
};
|
||||
canvas2.Children.Add(rectangle);
|
||||
var container2 = new AdornerContainer(child)
|
||||
{
|
||||
Child = canvas2
|
||||
};
|
||||
overlay = container2;
|
||||
SetOverlay(originalChild, overlay);
|
||||
AdornerLayer.GetAdornerLayer(root).Add(container2);
|
||||
TransferLocalValue(child, UIElement.RenderTransformProperty, CacheDuringOverlayProperty);
|
||||
child.RenderTransform = new TranslateTransform(-10000.0, -10000.0);
|
||||
canvas2.RenderTransform = new TranslateTransform(10000.0, 10000.0);
|
||||
child = rectangle;
|
||||
}
|
||||
|
||||
var parentRect = newTagData.ParentRect;
|
||||
var transitionStoryboard =
|
||||
CreateTransitionStoryboard(child, usingBeforeLoaded, ref parentRect, ref rect);
|
||||
TransitionStoryboardDictionary.Add(tag, transitionStoryboard);
|
||||
transitionStoryboard.Completed += delegate
|
||||
{
|
||||
if (TransitionStoryboardDictionary.TryGetValue(tag, out var storyboard1) &&
|
||||
Equals(storyboard1, transitionStoryboard))
|
||||
{
|
||||
TransitionStoryboardDictionary.Remove(tag);
|
||||
transitionStoryboard.Stop();
|
||||
RemoveTransform(child);
|
||||
child.InvalidateMeasure();
|
||||
if (overlay != null)
|
||||
{
|
||||
AdornerLayer.GetAdornerLayer(root).Remove((AdornerContainer) overlay);
|
||||
TransferLocalValue(originalChild, CacheDuringOverlayProperty,
|
||||
UIElement.RenderTransformProperty);
|
||||
SetOverlay(originalChild, null);
|
||||
}
|
||||
}
|
||||
};
|
||||
transitionStoryboard.Begin();
|
||||
}
|
||||
|
||||
data.ParentRect = newTagData.ParentRect;
|
||||
data.AppRect = newTagData.AppRect;
|
||||
data.Parent = newTagData.Parent;
|
||||
data.Child = newTagData.Child;
|
||||
data.Timestamp = newTagData.Timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public abstract class FluidMoveBehaviorBase : Behavior<FrameworkElement>
|
||||
{
|
||||
public static readonly DependencyProperty AppliesToProperty = DependencyProperty.Register("AppliesTo",
|
||||
typeof(FluidMoveScope), typeof(FluidMoveBehaviorBase), new PropertyMetadata(FluidMoveScope.Self));
|
||||
|
||||
protected static readonly DependencyProperty IdentityTagProperty =
|
||||
DependencyProperty.RegisterAttached("IdentityTag", typeof(object), typeof(FluidMoveBehaviorBase),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("IsActive",
|
||||
typeof(bool), typeof(FluidMoveBehaviorBase), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
private static DateTime LastPurgeTick = DateTime.MinValue;
|
||||
|
||||
private static readonly TimeSpan MinTickDelta = TimeSpan.FromSeconds(0.5);
|
||||
|
||||
private static DateTime NextToLastPurgeTick = DateTime.MinValue;
|
||||
|
||||
internal static Dictionary<object, TagData> TagDictionary = new Dictionary<object, TagData>();
|
||||
|
||||
public static readonly DependencyProperty TagPathProperty = DependencyProperty.Register("TagPath",
|
||||
typeof(string), typeof(FluidMoveBehaviorBase), new PropertyMetadata(string.Empty));
|
||||
|
||||
public static readonly DependencyProperty TagProperty = DependencyProperty.Register("Tag",
|
||||
typeof(TagType), typeof(FluidMoveBehaviorBase), new PropertyMetadata(TagType.Element));
|
||||
|
||||
public FluidMoveScope AppliesTo
|
||||
{
|
||||
get =>
|
||||
(FluidMoveScope) GetValue(AppliesToProperty);
|
||||
set => SetValue(AppliesToProperty, value);
|
||||
}
|
||||
|
||||
public bool IsActive
|
||||
{
|
||||
get =>
|
||||
(bool) GetValue(IsActiveProperty);
|
||||
set => SetValue(IsActiveProperty, value);
|
||||
}
|
||||
|
||||
protected virtual bool ShouldSkipInitialLayout =>
|
||||
Tag == TagType.DataContext;
|
||||
|
||||
public TagType Tag
|
||||
{
|
||||
get =>
|
||||
(TagType) GetValue(TagProperty);
|
||||
set => SetValue(TagProperty, value);
|
||||
}
|
||||
|
||||
public string TagPath
|
||||
{
|
||||
get =>
|
||||
(string) GetValue(TagPathProperty);
|
||||
set => SetValue(TagPathProperty, value);
|
||||
}
|
||||
|
||||
private void AssociatedObject_LayoutUpdated(object sender, EventArgs e)
|
||||
{
|
||||
if (IsActive)
|
||||
{
|
||||
if (DateTime.Now - LastPurgeTick >= MinTickDelta)
|
||||
{
|
||||
List<object> list = null;
|
||||
foreach (var pair in TagDictionary)
|
||||
if (pair.Value.Timestamp < NextToLastPurgeTick)
|
||||
{
|
||||
if (list == null) list = new List<object>();
|
||||
list.Add(pair.Key);
|
||||
}
|
||||
|
||||
if (list != null)
|
||||
foreach (var obj2 in list)
|
||||
TagDictionary.Remove(obj2);
|
||||
NextToLastPurgeTick = LastPurgeTick;
|
||||
LastPurgeTick = DateTime.Now;
|
||||
}
|
||||
|
||||
if (AppliesTo == FluidMoveScope.Self)
|
||||
{
|
||||
UpdateLayoutTransition(AssociatedObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AssociatedObject is Panel associatedObject)
|
||||
foreach (FrameworkElement element in associatedObject.Children)
|
||||
UpdateLayoutTransition(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void EnsureTags(FrameworkElement child)
|
||||
{
|
||||
if (Tag == TagType.DataContext &&
|
||||
!(child.ReadLocalValue(IdentityTagProperty) is BindingExpression))
|
||||
child.SetBinding(IdentityTagProperty, new Binding(TagPath));
|
||||
}
|
||||
|
||||
protected static object GetIdentityTag(DependencyObject obj)
|
||||
{
|
||||
return obj.GetValue(IdentityTagProperty);
|
||||
}
|
||||
|
||||
private static FrameworkElement GetVisualRoot(FrameworkElement child)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!(VisualTreeHelper.GetParent(child) is FrameworkElement parent)) return child;
|
||||
if (AdornerLayer.GetAdornerLayer(parent) == null) return child;
|
||||
child = parent;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
AssociatedObject.LayoutUpdated += AssociatedObject_LayoutUpdated;
|
||||
}
|
||||
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
base.OnDetaching();
|
||||
AssociatedObject.LayoutUpdated -= AssociatedObject_LayoutUpdated;
|
||||
}
|
||||
|
||||
protected static void SetIdentityTag(DependencyObject obj, object value)
|
||||
{
|
||||
obj.SetValue(IdentityTagProperty, value);
|
||||
}
|
||||
|
||||
internal static Rect TranslateRect(Rect rect, FrameworkElement from, FrameworkElement to)
|
||||
{
|
||||
if (from == null || to == null) return rect;
|
||||
var point = new Point(rect.Left, rect.Top);
|
||||
point = from.TransformToVisual(to).Transform(point);
|
||||
return new Rect(point.X, point.Y, rect.Width, rect.Height);
|
||||
}
|
||||
|
||||
private void UpdateLayoutTransition(FrameworkElement child)
|
||||
{
|
||||
if (child.Visibility != Visibility.Collapsed && child.IsLoaded || !ShouldSkipInitialLayout)
|
||||
{
|
||||
var visualRoot = GetVisualRoot(child);
|
||||
var newTagData = new TagData
|
||||
{
|
||||
Parent = VisualTreeHelper.GetParent(child) as FrameworkElement,
|
||||
ParentRect = ExtendedVisualStateManager.GetLayoutRect(child),
|
||||
Child = child,
|
||||
Timestamp = DateTime.Now
|
||||
};
|
||||
try
|
||||
{
|
||||
newTagData.AppRect = TranslateRect(newTagData.ParentRect, newTagData.Parent, visualRoot);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
if (ShouldSkipInitialLayout) return;
|
||||
}
|
||||
|
||||
EnsureTags(child);
|
||||
var identityTag = GetIdentityTag(child) ?? child;
|
||||
UpdateLayoutTransitionCore(child, visualRoot, identityTag, newTagData);
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract void UpdateLayoutTransitionCore(FrameworkElement child, FrameworkElement root,
|
||||
object tag, TagData newTagData);
|
||||
|
||||
|
||||
internal class TagData
|
||||
{
|
||||
public Rect AppRect { get; set; }
|
||||
|
||||
public FrameworkElement Child { get; set; }
|
||||
|
||||
public object InitialTag { get; set; }
|
||||
|
||||
public FrameworkElement Parent { get; set; }
|
||||
|
||||
public Rect ParentRect { get; set; }
|
||||
|
||||
public DateTime Timestamp { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public enum FluidMoveScope
|
||||
{
|
||||
Self,
|
||||
Children
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public interface IAttachedObject
|
||||
{
|
||||
void Attach(DependencyObject dependencyObject);
|
||||
void Detach();
|
||||
|
||||
DependencyObject AssociatedObject { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*refrence from https://github.com/lbugnion/mvvmlight*/
|
||||
|
||||
//The MIT License(MIT)
|
||||
|
||||
//Copyright(c) 2009 - 2018 Laurent Bugnion
|
||||
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
//of this software and associated documentation files (the "Software"), to deal
|
||||
//in the Software without restriction, including without limitation the rights
|
||||
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
//copies of the Software, and to permit persons to whom the Software is
|
||||
//furnished to do so, subject to the following conditions:
|
||||
|
||||
//The above copyright notice and this permission notice shall be included in all
|
||||
//copies or substantial portions of the Software.
|
||||
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public interface IEventArgsConverter
|
||||
{
|
||||
object Convert(object value, object parameter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public static class Interaction
|
||||
{
|
||||
private static readonly DependencyProperty TriggersProperty =
|
||||
DependencyProperty.RegisterAttached("ShadowTriggers", typeof(TriggerCollection), typeof(Interaction),
|
||||
new FrameworkPropertyMetadata(OnTriggersChanged));
|
||||
|
||||
private static readonly DependencyProperty BehaviorsProperty =
|
||||
DependencyProperty.RegisterAttached("ShadowBehaviors", typeof(BehaviorCollection), typeof(Interaction),
|
||||
new FrameworkPropertyMetadata(OnBehaviorsChanged));
|
||||
|
||||
internal static bool ShouldRunInDesignMode { get; set; }
|
||||
|
||||
public static TriggerCollection GetTriggers(DependencyObject obj)
|
||||
{
|
||||
var triggerCollection = (TriggerCollection) obj.GetValue(TriggersProperty);
|
||||
if (triggerCollection == null)
|
||||
{
|
||||
triggerCollection = new TriggerCollection();
|
||||
obj.SetValue(TriggersProperty, triggerCollection);
|
||||
}
|
||||
return triggerCollection;
|
||||
}
|
||||
|
||||
public static BehaviorCollection GetBehaviors(DependencyObject obj)
|
||||
{
|
||||
var behaviorCollection = (BehaviorCollection) obj.GetValue(BehaviorsProperty);
|
||||
if (behaviorCollection == null)
|
||||
{
|
||||
behaviorCollection = new BehaviorCollection();
|
||||
obj.SetValue(BehaviorsProperty, behaviorCollection);
|
||||
}
|
||||
return behaviorCollection;
|
||||
}
|
||||
|
||||
private static void OnBehaviorsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
var oldValue = (BehaviorCollection) args.OldValue;
|
||||
var newValue = (BehaviorCollection) args.NewValue;
|
||||
if (Equals(oldValue, newValue))
|
||||
return;
|
||||
if (oldValue?.AssociatedObject != null)
|
||||
oldValue.Detach();
|
||||
if (newValue == null || obj == null)
|
||||
return;
|
||||
if (newValue.AssociatedObject != null)
|
||||
throw new InvalidOperationException(ExceptionStringTable
|
||||
.CannotHostBehaviorCollectionMultipleTimesExceptionMessage);
|
||||
newValue.Attach(obj);
|
||||
}
|
||||
|
||||
private static void OnTriggersChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
var oldValue = args.OldValue as TriggerCollection;
|
||||
var newValue = args.NewValue as TriggerCollection;
|
||||
if (Equals(oldValue, newValue))
|
||||
return;
|
||||
if (oldValue?.AssociatedObject != null)
|
||||
oldValue.Detach();
|
||||
if (newValue == null || obj == null)
|
||||
return;
|
||||
if (newValue.AssociatedObject != null)
|
||||
throw new InvalidOperationException(ExceptionStringTable
|
||||
.CannotHostTriggerCollectionMultipleTimesExceptionMessage);
|
||||
newValue.Attach(obj);
|
||||
}
|
||||
|
||||
internal static bool IsElementLoaded(FrameworkElement element)
|
||||
{
|
||||
return element.IsLoaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public sealed class InvokeCommandAction : TriggerAction<DependencyObject>
|
||||
{
|
||||
public static readonly DependencyProperty CommandProperty =
|
||||
DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(InvokeCommandAction), null);
|
||||
|
||||
public static readonly DependencyProperty CommandParameterProperty =
|
||||
DependencyProperty.Register(nameof(CommandParameter), typeof(object), typeof(InvokeCommandAction),
|
||||
null);
|
||||
|
||||
private string _commandName;
|
||||
|
||||
public string CommandName
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _commandName;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (CommandName == value)
|
||||
return;
|
||||
WritePreamble();
|
||||
_commandName = value;
|
||||
WritePostscript();
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand Command
|
||||
{
|
||||
get => (ICommand)GetValue(CommandProperty);
|
||||
set => SetValue(CommandProperty, value);
|
||||
}
|
||||
|
||||
public object CommandParameter
|
||||
{
|
||||
get => GetValue(CommandParameterProperty);
|
||||
set => SetValue(CommandParameterProperty, value);
|
||||
}
|
||||
|
||||
protected override void Invoke(object parameter)
|
||||
{
|
||||
if (AssociatedObject == null)
|
||||
return;
|
||||
var command = ResolveCommand();
|
||||
if (command == null || !command.CanExecute(CommandParameter))
|
||||
return;
|
||||
command.Execute(CommandParameter);
|
||||
}
|
||||
|
||||
private ICommand ResolveCommand()
|
||||
{
|
||||
var command = (ICommand)null;
|
||||
if (Command != null)
|
||||
command = Command;
|
||||
else if (AssociatedObject != null)
|
||||
foreach (var property in AssociatedObject.GetType()
|
||||
.GetProperties(BindingFlags.Instance | BindingFlags.Public))
|
||||
if (typeof(ICommand).IsAssignableFrom(property.PropertyType) &&
|
||||
string.Equals(property.Name, CommandName, StringComparison.Ordinal))
|
||||
command = (ICommand)property.GetValue(AssociatedObject, null);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public class MouseDragElementBehavior : Behavior<FrameworkElement>
|
||||
{
|
||||
public static readonly DependencyProperty ConstrainToParentBoundsProperty =
|
||||
DependencyProperty.Register("ConstrainToParentBounds", typeof(bool), typeof(MouseDragElementBehavior),
|
||||
new PropertyMetadata(ValueBoxes.FalseBox, OnConstrainToParentBoundsChanged));
|
||||
|
||||
public static readonly DependencyProperty XProperty = DependencyProperty.Register("X", typeof(double),
|
||||
typeof(MouseDragElementBehavior), new PropertyMetadata(double.PositiveInfinity, OnXChanged));
|
||||
|
||||
public static readonly DependencyProperty YProperty = DependencyProperty.Register("Y", typeof(double),
|
||||
typeof(MouseDragElementBehavior), new PropertyMetadata(double.PositiveInfinity, OnYChanged));
|
||||
|
||||
// Fields
|
||||
private Transform _cachedRenderTransform;
|
||||
|
||||
private Point _relativePosition;
|
||||
private bool _settingPosition;
|
||||
|
||||
// Properties
|
||||
|
||||
public bool ConstrainToParentBounds
|
||||
{
|
||||
get =>
|
||||
(bool) GetValue(ConstrainToParentBoundsProperty);
|
||||
set => SetValue(ConstrainToParentBoundsProperty, value);
|
||||
}
|
||||
|
||||
private Rect ElementBounds
|
||||
{
|
||||
get
|
||||
{
|
||||
var layoutRect = ExtendedVisualStateManager.GetLayoutRect(AssociatedObject);
|
||||
return new Rect(new Point(0.0, 0.0), new Size(layoutRect.Width, layoutRect.Height));
|
||||
}
|
||||
}
|
||||
|
||||
private FrameworkElement ParentElement =>
|
||||
AssociatedObject.Parent as FrameworkElement;
|
||||
|
||||
private Transform RenderTransform
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cachedRenderTransform == null ||
|
||||
!ReferenceEquals(_cachedRenderTransform, AssociatedObject.RenderTransform))
|
||||
{
|
||||
var transform = CloneTransform(AssociatedObject.RenderTransform);
|
||||
RenderTransform = transform;
|
||||
}
|
||||
return _cachedRenderTransform;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!Equals(_cachedRenderTransform, value))
|
||||
{
|
||||
_cachedRenderTransform = value;
|
||||
AssociatedObject.RenderTransform = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private UIElement RootElement
|
||||
{
|
||||
get
|
||||
{
|
||||
DependencyObject associatedObject = AssociatedObject;
|
||||
for (var obj3 = associatedObject; obj3 != null; obj3 = VisualTreeHelper.GetParent(associatedObject))
|
||||
associatedObject = obj3;
|
||||
return associatedObject as UIElement;
|
||||
}
|
||||
}
|
||||
|
||||
public double X
|
||||
{
|
||||
get =>
|
||||
(double) GetValue(XProperty);
|
||||
set => SetValue(XProperty, value);
|
||||
}
|
||||
|
||||
public double Y
|
||||
{
|
||||
get =>
|
||||
(double) GetValue(YProperty);
|
||||
set => SetValue(YProperty, value);
|
||||
}
|
||||
|
||||
// Events
|
||||
public event MouseEventHandler DragBegun;
|
||||
|
||||
public event MouseEventHandler DragFinished;
|
||||
|
||||
public event MouseEventHandler Dragging;
|
||||
|
||||
// Methods
|
||||
private void ApplyTranslation(double x, double y)
|
||||
{
|
||||
if (ParentElement != null)
|
||||
{
|
||||
var point = TransformAsVector(RootElement.TransformToVisual(ParentElement), x, y);
|
||||
x = point.X;
|
||||
y = point.Y;
|
||||
if (ConstrainToParentBounds)
|
||||
{
|
||||
var parentElement = ParentElement;
|
||||
var rect = new Rect(0.0, 0.0, parentElement.ActualWidth, parentElement.ActualHeight);
|
||||
var transform2 = AssociatedObject.TransformToVisual(parentElement);
|
||||
var elementBounds = ElementBounds;
|
||||
var rect3 = transform2.TransformBounds(elementBounds);
|
||||
rect3.X += x;
|
||||
rect3.Y += y;
|
||||
if (!RectContainsRect(rect, rect3))
|
||||
{
|
||||
if (rect3.X < rect.Left)
|
||||
{
|
||||
var num = rect3.X - rect.Left;
|
||||
x -= num;
|
||||
}
|
||||
else if (rect3.Right > rect.Right)
|
||||
{
|
||||
var num2 = rect3.Right - rect.Right;
|
||||
x -= num2;
|
||||
}
|
||||
if (rect3.Y < rect.Top)
|
||||
{
|
||||
var num3 = rect3.Y - rect.Top;
|
||||
y -= num3;
|
||||
}
|
||||
else if (rect3.Bottom > rect.Bottom)
|
||||
{
|
||||
var num4 = rect3.Bottom - rect.Bottom;
|
||||
y -= num4;
|
||||
}
|
||||
}
|
||||
}
|
||||
ApplyTranslationTransform(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ApplyTranslationTransform(double x, double y)
|
||||
{
|
||||
var renderTransform = RenderTransform;
|
||||
var transform2 = renderTransform as TranslateTransform;
|
||||
if (transform2 == null)
|
||||
{
|
||||
var group = renderTransform as TransformGroup;
|
||||
var transform3 = renderTransform as MatrixTransform;
|
||||
if (group != null)
|
||||
{
|
||||
if (group.Children.Count > 0)
|
||||
transform2 = group.Children[group.Children.Count - 1] as TranslateTransform;
|
||||
if (transform2 == null)
|
||||
{
|
||||
transform2 = new TranslateTransform();
|
||||
group.Children.Add(transform2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (transform3 != null)
|
||||
{
|
||||
var matrix = transform3.Matrix;
|
||||
matrix.OffsetX += x;
|
||||
matrix.OffsetY += y;
|
||||
var transform4 = new MatrixTransform
|
||||
{
|
||||
Matrix = matrix
|
||||
};
|
||||
RenderTransform = transform4;
|
||||
return;
|
||||
}
|
||||
var group2 = new TransformGroup();
|
||||
transform2 = new TranslateTransform();
|
||||
if (renderTransform != null)
|
||||
group2.Children.Add(renderTransform);
|
||||
group2.Children.Add(transform2);
|
||||
RenderTransform = group2;
|
||||
}
|
||||
}
|
||||
transform2.X += x;
|
||||
transform2.Y += y;
|
||||
}
|
||||
|
||||
internal static Transform CloneTransform(Transform transform)
|
||||
{
|
||||
if (transform == null)
|
||||
return null;
|
||||
if (transform is ScaleTransform transform2)
|
||||
return new ScaleTransform
|
||||
{
|
||||
CenterX = transform2.CenterX,
|
||||
CenterY = transform2.CenterY,
|
||||
ScaleX = transform2.ScaleX,
|
||||
ScaleY = transform2.ScaleY
|
||||
};
|
||||
if (transform is RotateTransform transform3)
|
||||
return new RotateTransform
|
||||
{
|
||||
Angle = transform3.Angle,
|
||||
CenterX = transform3.CenterX,
|
||||
CenterY = transform3.CenterY
|
||||
};
|
||||
if (transform is SkewTransform transform4)
|
||||
return new SkewTransform
|
||||
{
|
||||
AngleX = transform4.AngleX,
|
||||
AngleY = transform4.AngleY,
|
||||
CenterX = transform4.CenterX,
|
||||
CenterY = transform4.CenterY
|
||||
};
|
||||
if (transform is TranslateTransform transform5)
|
||||
return new TranslateTransform
|
||||
{
|
||||
X = transform5.X,
|
||||
Y = transform5.Y
|
||||
};
|
||||
if (transform is MatrixTransform transform6)
|
||||
return new MatrixTransform {Matrix = transform6.Matrix};
|
||||
if (!(transform is TransformGroup group))
|
||||
return null;
|
||||
var group2 = new TransformGroup();
|
||||
foreach (var transform12 in group.Children)
|
||||
group2.Children.Add(CloneTransform(transform12));
|
||||
return group2;
|
||||
}
|
||||
|
||||
internal void EndDrag()
|
||||
{
|
||||
AssociatedObject.MouseMove -= OnMouseMove;
|
||||
AssociatedObject.LostMouseCapture -= OnLostMouseCapture;
|
||||
AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonUpEvent,
|
||||
new MouseButtonEventHandler(OnMouseLeftButtonUp));
|
||||
}
|
||||
|
||||
private static Point GetTransformOffset(GeneralTransform transform)
|
||||
{
|
||||
return transform.Transform(new Point(0.0, 0.0));
|
||||
}
|
||||
|
||||
internal void HandleDrag(Point newPositionInElementCoordinates)
|
||||
{
|
||||
var x = newPositionInElementCoordinates.X - _relativePosition.X;
|
||||
var y = newPositionInElementCoordinates.Y - _relativePosition.Y;
|
||||
var point = TransformAsVector(AssociatedObject.TransformToVisual(RootElement), x, y);
|
||||
_settingPosition = true;
|
||||
ApplyTranslation(point.X, point.Y);
|
||||
UpdatePosition();
|
||||
_settingPosition = false;
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
AssociatedObject.AddHandler(UIElement.MouseLeftButtonDownEvent,
|
||||
new MouseButtonEventHandler(OnMouseLeftButtonDown), false);
|
||||
}
|
||||
|
||||
private static void OnConstrainToParentBoundsChanged(object sender, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
var behavior = (MouseDragElementBehavior) sender;
|
||||
behavior.UpdatePosition(new Point(behavior.X, behavior.Y));
|
||||
}
|
||||
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonDownEvent,
|
||||
new MouseButtonEventHandler(OnMouseLeftButtonDown));
|
||||
}
|
||||
|
||||
private void OnLostMouseCapture(object sender, MouseEventArgs e)
|
||||
{
|
||||
EndDrag();
|
||||
DragFinished?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
StartDrag(e.GetPosition(AssociatedObject));
|
||||
DragBegun?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
AssociatedObject.ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
private void OnMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
HandleDrag(e.GetPosition(AssociatedObject));
|
||||
Dragging?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private static void OnXChanged(object sender, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
var behavior = (MouseDragElementBehavior) sender;
|
||||
behavior.UpdatePosition(new Point((double) args.NewValue, behavior.Y));
|
||||
}
|
||||
|
||||
private static void OnYChanged(object sender, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
var behavior = (MouseDragElementBehavior) sender;
|
||||
behavior.UpdatePosition(new Point(behavior.X, (double) args.NewValue));
|
||||
}
|
||||
|
||||
private static bool RectContainsRect(Rect rect1, Rect rect2)
|
||||
{
|
||||
if (rect1.IsEmpty || rect2.IsEmpty)
|
||||
return false;
|
||||
return rect1.X <= rect2.X && rect1.Y <= rect2.Y && rect1.X + rect1.Width >= rect2.X + rect2.Width &&
|
||||
rect1.Y + rect1.Height >= rect2.Y + rect2.Height;
|
||||
}
|
||||
|
||||
internal void StartDrag(Point positionInElementCoordinates)
|
||||
{
|
||||
_relativePosition = positionInElementCoordinates;
|
||||
AssociatedObject.CaptureMouse();
|
||||
AssociatedObject.MouseMove += OnMouseMove;
|
||||
AssociatedObject.LostMouseCapture += OnLostMouseCapture;
|
||||
AssociatedObject.AddHandler(UIElement.MouseLeftButtonUpEvent,
|
||||
new MouseButtonEventHandler(OnMouseLeftButtonUp), false);
|
||||
}
|
||||
|
||||
private static Point TransformAsVector(GeneralTransform transform, double x, double y)
|
||||
{
|
||||
var point = transform.Transform(new Point(0.0, 0.0));
|
||||
var point2 = transform.Transform(new Point(x, y));
|
||||
return new Point(point2.X - point.X, point2.Y - point.Y);
|
||||
}
|
||||
|
||||
private void UpdatePosition()
|
||||
{
|
||||
var transformOffset = GetTransformOffset(AssociatedObject.TransformToVisual(RootElement));
|
||||
X = transformOffset.X;
|
||||
Y = transformOffset.Y;
|
||||
}
|
||||
|
||||
private void UpdatePosition(Point point)
|
||||
{
|
||||
if (!_settingPosition && AssociatedObject != null)
|
||||
{
|
||||
var transformOffset = GetTransformOffset(AssociatedObject.TransformToVisual(RootElement));
|
||||
var x = double.IsNaN(point.X) ? 0.0 : point.X - transformOffset.X;
|
||||
var y = double.IsNaN(point.Y) ? 0.0 : point.Y - transformOffset.Y;
|
||||
ApplyTranslation(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
internal sealed class NameResolvedEventArgs : EventArgs
|
||||
{
|
||||
public NameResolvedEventArgs(object oldObject, object newObject)
|
||||
{
|
||||
OldObject = oldObject;
|
||||
NewObject = newObject;
|
||||
}
|
||||
|
||||
public object NewObject { get; }
|
||||
|
||||
public object OldObject { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
internal sealed class NameResolver
|
||||
{
|
||||
private string _name;
|
||||
private FrameworkElement _nameScopeReferenceElement;
|
||||
|
||||
private FrameworkElement ActualNameScopeReferenceElement
|
||||
{
|
||||
get
|
||||
{
|
||||
if (NameScopeReferenceElement != null && Interaction.IsElementLoaded(NameScopeReferenceElement))
|
||||
return GetActualNameScopeReference(NameScopeReferenceElement);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasAttempedResolve { get; set; }
|
||||
|
||||
public string Name
|
||||
{
|
||||
get =>
|
||||
_name;
|
||||
set
|
||||
{
|
||||
var oldObject = Object;
|
||||
_name = value;
|
||||
UpdateObjectFromName(oldObject);
|
||||
}
|
||||
}
|
||||
|
||||
public FrameworkElement NameScopeReferenceElement
|
||||
{
|
||||
get =>
|
||||
_nameScopeReferenceElement;
|
||||
set
|
||||
{
|
||||
var nameScopeReferenceElement = NameScopeReferenceElement;
|
||||
_nameScopeReferenceElement = value;
|
||||
OnNameScopeReferenceElementChanged(nameScopeReferenceElement);
|
||||
}
|
||||
}
|
||||
|
||||
public DependencyObject Object
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Name) && HasAttempedResolve)
|
||||
return NameScopeReferenceElement;
|
||||
return ResolvedObject;
|
||||
}
|
||||
}
|
||||
|
||||
private bool PendingReferenceElementLoad { get; set; }
|
||||
|
||||
private DependencyObject ResolvedObject { get; set; }
|
||||
|
||||
public event EventHandler<NameResolvedEventArgs> ResolvedElementChanged;
|
||||
|
||||
private FrameworkElement GetActualNameScopeReference(FrameworkElement initialReferenceElement)
|
||||
{
|
||||
var element = initialReferenceElement;
|
||||
if (!IsNameScope(initialReferenceElement))
|
||||
return element;
|
||||
return initialReferenceElement.Parent as FrameworkElement ?? element;
|
||||
}
|
||||
|
||||
private bool IsNameScope(FrameworkElement frameworkElement)
|
||||
{
|
||||
return frameworkElement.Parent is FrameworkElement parent && parent.FindName(Name) != null;
|
||||
}
|
||||
|
||||
private void OnNameScopeReferenceElementChanged(FrameworkElement oldNameScopeReference)
|
||||
{
|
||||
if (PendingReferenceElementLoad)
|
||||
{
|
||||
oldNameScopeReference.Loaded -= OnNameScopeReferenceLoaded;
|
||||
PendingReferenceElementLoad = false;
|
||||
}
|
||||
HasAttempedResolve = false;
|
||||
UpdateObjectFromName(Object);
|
||||
}
|
||||
|
||||
private void OnNameScopeReferenceLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
PendingReferenceElementLoad = false;
|
||||
NameScopeReferenceElement.Loaded -= OnNameScopeReferenceLoaded;
|
||||
UpdateObjectFromName(Object);
|
||||
}
|
||||
|
||||
private void OnObjectChanged(DependencyObject oldTarget, DependencyObject newTarget)
|
||||
{
|
||||
ResolvedElementChanged?.Invoke(this, new NameResolvedEventArgs(oldTarget, newTarget));
|
||||
}
|
||||
|
||||
private void UpdateObjectFromName(DependencyObject oldObject)
|
||||
{
|
||||
DependencyObject obj2 = null;
|
||||
ResolvedObject = null;
|
||||
if (NameScopeReferenceElement != null)
|
||||
{
|
||||
if (!Interaction.IsElementLoaded(NameScopeReferenceElement))
|
||||
{
|
||||
NameScopeReferenceElement.Loaded += OnNameScopeReferenceLoaded;
|
||||
PendingReferenceElementLoad = true;
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(Name))
|
||||
{
|
||||
var actualNameScopeReferenceElement = ActualNameScopeReferenceElement;
|
||||
if (actualNameScopeReferenceElement != null)
|
||||
obj2 = actualNameScopeReferenceElement.FindName(Name) as DependencyObject;
|
||||
}
|
||||
}
|
||||
HasAttempedResolve = true;
|
||||
ResolvedObject = obj2;
|
||||
if (!Equals(oldObject, Object))
|
||||
OnObjectChanged(oldObject, Object);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public class PreviewInvokeEventArgs : EventArgs
|
||||
{
|
||||
public bool Cancelling { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public class RoutedEventTrigger : EventTriggerBase<DependencyObject>
|
||||
{
|
||||
public RoutedEvent RoutedEvent { get; set; }
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
var behavior = AssociatedObject as Behavior;
|
||||
var associatedElement = AssociatedObject as FrameworkElement;
|
||||
if (behavior != null) associatedElement = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
|
||||
if (associatedElement == null) throw new ArgumentException();
|
||||
if (RoutedEvent != null) associatedElement.AddHandler(RoutedEvent, new RoutedEventHandler(OnRoutedEvent));
|
||||
}
|
||||
|
||||
private void OnRoutedEvent(object sender, RoutedEventArgs args)
|
||||
{
|
||||
OnEvent(args);
|
||||
}
|
||||
|
||||
protected override string GetEventName()
|
||||
{
|
||||
return RoutedEvent.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public enum TagType
|
||||
{
|
||||
Element,
|
||||
DataContext
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Effects;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public abstract class TransitionEffect : ShaderEffect
|
||||
{
|
||||
// Fields
|
||||
public static readonly DependencyProperty InputProperty =
|
||||
RegisterPixelShaderSamplerProperty("Input", typeof(TransitionEffect), 0, SamplingMode.NearestNeighbor);
|
||||
|
||||
public static readonly DependencyProperty OldImageProperty =
|
||||
RegisterPixelShaderSamplerProperty("OldImage", typeof(TransitionEffect), 1, SamplingMode.NearestNeighbor);
|
||||
|
||||
public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register("Progress",
|
||||
typeof(double), typeof(TransitionEffect), new PropertyMetadata(0.0, PixelShaderConstantCallback(0)));
|
||||
|
||||
// Methods
|
||||
protected TransitionEffect()
|
||||
{
|
||||
UpdateShaderValue(InputProperty);
|
||||
UpdateShaderValue(OldImageProperty);
|
||||
UpdateShaderValue(ProgressProperty);
|
||||
}
|
||||
|
||||
// Properties
|
||||
public Brush Input
|
||||
{
|
||||
get =>
|
||||
(Brush) GetValue(InputProperty);
|
||||
set => SetValue(InputProperty, value);
|
||||
}
|
||||
|
||||
public Brush OldImage
|
||||
{
|
||||
get =>
|
||||
(Brush) GetValue(OldImageProperty);
|
||||
set => SetValue(OldImageProperty, value);
|
||||
}
|
||||
|
||||
public double Progress
|
||||
{
|
||||
get =>
|
||||
(double) GetValue(ProgressProperty);
|
||||
set => SetValue(ProgressProperty, value);
|
||||
}
|
||||
|
||||
public new TransitionEffect CloneCurrentValue()
|
||||
{
|
||||
return (TransitionEffect) base.CloneCurrentValue();
|
||||
}
|
||||
|
||||
protected abstract TransitionEffect DeepCopy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public abstract class TriggerAction<T> : TriggerAction where T : DependencyObject
|
||||
{
|
||||
protected TriggerAction() : base(typeof(T))
|
||||
{
|
||||
}
|
||||
|
||||
#pragma warning disable 108,114
|
||||
protected T AssociatedObject => (T)base.AssociatedObject;
|
||||
#pragma warning restore 108,114
|
||||
|
||||
protected sealed override Type AssociatedObjectTypeConstraint =>
|
||||
base.AssociatedObjectTypeConstraint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
[DefaultTrigger(typeof(UIElement), typeof(EventTrigger), "MouseLeftButtonDown")]
|
||||
[DefaultTrigger(typeof(ButtonBase), typeof(EventTrigger), "Click")]
|
||||
public abstract class TriggerAction : Animatable, IAttachedObject
|
||||
{
|
||||
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.Register("IsEnabled",
|
||||
typeof(bool), typeof(TriggerAction), new FrameworkPropertyMetadata(true));
|
||||
|
||||
private readonly Type _associatedObjectTypeConstraint;
|
||||
|
||||
private DependencyObject _associatedObject;
|
||||
private bool _isHosted;
|
||||
|
||||
internal TriggerAction(Type associatedObjectTypeConstraint)
|
||||
{
|
||||
_associatedObjectTypeConstraint = associatedObjectTypeConstraint;
|
||||
}
|
||||
|
||||
protected DependencyObject AssociatedObject
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedObject;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Type AssociatedObjectTypeConstraint
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedObjectTypeConstraint;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get =>
|
||||
(bool) GetValue(IsEnabledProperty);
|
||||
set => SetValue(IsEnabledProperty, value);
|
||||
}
|
||||
|
||||
internal bool IsHosted
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _isHosted;
|
||||
}
|
||||
set
|
||||
{
|
||||
WritePreamble();
|
||||
_isHosted = value;
|
||||
WritePostscript();
|
||||
}
|
||||
}
|
||||
|
||||
public void Attach(DependencyObject dependencyObject)
|
||||
{
|
||||
if (!Equals(dependencyObject, AssociatedObject))
|
||||
{
|
||||
if (AssociatedObject != null)
|
||||
throw new InvalidOperationException(ExceptionStringTable
|
||||
.CannotHostTriggerActionMultipleTimesExceptionMessage);
|
||||
if (dependencyObject != null &&
|
||||
!AssociatedObjectTypeConstraint.IsInstanceOfType(dependencyObject))
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.TypeConstraintViolatedExceptionMessage,
|
||||
new object[]
|
||||
{GetType().Name, dependencyObject.GetType().Name, AssociatedObjectTypeConstraint.Name}));
|
||||
WritePreamble();
|
||||
_associatedObject = dependencyObject;
|
||||
WritePostscript();
|
||||
OnAttached();
|
||||
}
|
||||
}
|
||||
|
||||
public void Detach()
|
||||
{
|
||||
OnDetaching();
|
||||
WritePreamble();
|
||||
_associatedObject = null;
|
||||
WritePostscript();
|
||||
}
|
||||
|
||||
DependencyObject IAttachedObject.AssociatedObject =>
|
||||
AssociatedObject;
|
||||
|
||||
internal void CallInvoke(object parameter)
|
||||
{
|
||||
if (IsEnabled)
|
||||
Invoke(parameter);
|
||||
}
|
||||
|
||||
protected override Freezable CreateInstanceCore()
|
||||
{
|
||||
return (Freezable) Activator.CreateInstance(GetType());
|
||||
}
|
||||
|
||||
protected abstract void Invoke(object parameter);
|
||||
|
||||
protected virtual void OnAttached()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnDetaching()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public class TriggerActionCollection : AttachableCollection<TriggerAction>
|
||||
{
|
||||
internal TriggerActionCollection()
|
||||
{
|
||||
}
|
||||
|
||||
protected override Freezable CreateInstanceCore()
|
||||
{
|
||||
return new TriggerActionCollection();
|
||||
}
|
||||
|
||||
internal override void ItemAdded(TriggerAction item)
|
||||
{
|
||||
if (item.IsHosted)
|
||||
throw new InvalidOperationException(ExceptionStringTable
|
||||
.CannotHostTriggerActionMultipleTimesExceptionMessage);
|
||||
if (AssociatedObject != null)
|
||||
item.Attach(AssociatedObject);
|
||||
item.IsHosted = true;
|
||||
}
|
||||
|
||||
internal override void ItemRemoved(TriggerAction item)
|
||||
{
|
||||
if (((IAttachedObject) item).AssociatedObject != null)
|
||||
item.Detach();
|
||||
item.IsHosted = false;
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
foreach (var action in this)
|
||||
action.Attach(AssociatedObject);
|
||||
}
|
||||
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
foreach (var action in this)
|
||||
action.Detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
[ContentProperty("Actions")]
|
||||
public abstract class TriggerBase : Animatable, IAttachedObject
|
||||
{
|
||||
private static readonly DependencyPropertyKey ActionsPropertyKey =
|
||||
DependencyProperty.RegisterReadOnly("Actions", typeof(TriggerActionCollection), typeof(TriggerBase),
|
||||
new FrameworkPropertyMetadata());
|
||||
|
||||
public static readonly DependencyProperty ActionsProperty = ActionsPropertyKey.DependencyProperty;
|
||||
|
||||
private DependencyObject _associatedObject;
|
||||
private readonly Type _associatedObjectTypeConstraint;
|
||||
|
||||
internal TriggerBase(Type associatedObjectTypeConstraint)
|
||||
{
|
||||
_associatedObjectTypeConstraint = associatedObjectTypeConstraint;
|
||||
var actions = new TriggerActionCollection();
|
||||
SetValue(ActionsPropertyKey, actions);
|
||||
}
|
||||
|
||||
public TriggerActionCollection Actions =>
|
||||
(TriggerActionCollection) GetValue(ActionsProperty);
|
||||
|
||||
protected DependencyObject AssociatedObject
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedObject;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Type AssociatedObjectTypeConstraint
|
||||
{
|
||||
get
|
||||
{
|
||||
ReadPreamble();
|
||||
return _associatedObjectTypeConstraint;
|
||||
}
|
||||
}
|
||||
|
||||
public void Attach(DependencyObject dependencyObject)
|
||||
{
|
||||
if (!Equals(dependencyObject, AssociatedObject))
|
||||
{
|
||||
if (AssociatedObject != null)
|
||||
throw new InvalidOperationException(ExceptionStringTable
|
||||
.CannotHostTriggerMultipleTimesExceptionMessage);
|
||||
if (dependencyObject != null &&
|
||||
!AssociatedObjectTypeConstraint.IsInstanceOfType(dependencyObject))
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
|
||||
ExceptionStringTable.TypeConstraintViolatedExceptionMessage,
|
||||
new object[]
|
||||
{GetType().Name, dependencyObject.GetType().Name, AssociatedObjectTypeConstraint.Name}));
|
||||
WritePreamble();
|
||||
_associatedObject = dependencyObject;
|
||||
WritePostscript();
|
||||
Actions.Attach(dependencyObject);
|
||||
OnAttached();
|
||||
}
|
||||
}
|
||||
|
||||
public void Detach()
|
||||
{
|
||||
OnDetaching();
|
||||
WritePreamble();
|
||||
_associatedObject = null;
|
||||
WritePostscript();
|
||||
Actions.Detach();
|
||||
}
|
||||
|
||||
DependencyObject IAttachedObject.AssociatedObject =>
|
||||
AssociatedObject;
|
||||
|
||||
public event EventHandler<PreviewInvokeEventArgs> PreviewInvoke;
|
||||
|
||||
protected override Freezable CreateInstanceCore()
|
||||
{
|
||||
return (Freezable) Activator.CreateInstance(GetType());
|
||||
}
|
||||
|
||||
protected void InvokeActions(object parameter)
|
||||
{
|
||||
if (PreviewInvoke != null)
|
||||
{
|
||||
var e = new PreviewInvokeEventArgs();
|
||||
PreviewInvoke(this, e);
|
||||
if (e.Cancelling)
|
||||
return;
|
||||
}
|
||||
foreach (var action in Actions)
|
||||
action.CallInvoke(parameter);
|
||||
}
|
||||
|
||||
protected virtual void OnAttached()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnDetaching()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
public sealed class TriggerCollection : AttachableCollection<TriggerBase>
|
||||
{
|
||||
internal TriggerCollection()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
foreach (var triggerBase in this)
|
||||
triggerBase.Attach(AssociatedObject);
|
||||
}
|
||||
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
foreach (var triggerBase in this)
|
||||
triggerBase.Detach();
|
||||
}
|
||||
|
||||
internal override void ItemAdded(TriggerBase item)
|
||||
{
|
||||
if (AssociatedObject == null)
|
||||
return;
|
||||
item.Attach(AssociatedObject);
|
||||
}
|
||||
|
||||
internal override void ItemRemoved(TriggerBase item)
|
||||
{
|
||||
if (((IAttachedObject) item).AssociatedObject == null)
|
||||
return;
|
||||
item.Detach();
|
||||
}
|
||||
|
||||
protected override Freezable CreateInstanceCore()
|
||||
{
|
||||
return new TriggerCollection();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace HandyControl.Interactivity
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public sealed class TypeConstraintAttribute : Attribute
|
||||
{
|
||||
public TypeConstraintAttribute(Type constraint)
|
||||
{
|
||||
Constraint = constraint;
|
||||
}
|
||||
|
||||
public Type Constraint { get; }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user