Skip to content

Instantly share code, notes, and snippets.

@zlatanov
Created December 27, 2019 14:43
Show Gist options
  • Save zlatanov/8ed191d517d2a84529f66b73347cd7ca to your computer and use it in GitHub Desktop.
Save zlatanov/8ed191d517d2a84529f66b73347cd7ca to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Data;
using Maverick.Windows;
using Expression = System.Linq.Expressions.Expression;
public static class DependencyPropertyRegistrar<TOwner> where TOwner : DependencyObject
{
public sealed class Property<TValue>
{
public delegate void ChangedCallback( TValue oldValue, TValue newValue );
internal Property( String propertyName )
{
m_propertyName = propertyName;
}
public Property<TValue> Default( TValue value )
{
m_default = value;
return this;
}
public Property<TValue> AffectsRender()
{
m_flags |= FrameworkPropertyMetadataOptions.AffectsRender;
return this;
}
public Property<TValue> AffectsMeasure()
{
m_flags |= FrameworkPropertyMetadataOptions.AffectsMeasure;
return this;
}
public Property<TValue> AffectsArrange()
{
m_flags |= FrameworkPropertyMetadataOptions.AffectsArrange;
return this;
}
public Property<TValue> BindsTwoWayByDefault()
{
m_flags |= FrameworkPropertyMetadataOptions.BindsTwoWayByDefault;
return this;
}
public Property<TValue> NotDataBindable()
{
m_flags |= FrameworkPropertyMetadataOptions.NotDataBindable;
return this;
}
public Property<TValue> Inheritable()
{
m_flags |= FrameworkPropertyMetadataOptions.Inherits;
return this;
}
public Property<TValue> Journal()
{
m_flags |= FrameworkPropertyMetadataOptions.Journal;
return this;
}
public Property<TValue> UpdateSource( UpdateSourceTrigger trigger )
{
m_updateSourceTrigger = trigger;
return this;
}
public Property<TValue> OnChange( PropertyChangedCallback<TOwner> callback )
{
m_changeCallback = callback;
return this;
}
public Property<TValue> OnChange( Expression<Func<TOwner, ChangedCallback>> expression )
{
var unaryExpression = (UnaryExpression)expression.Body;
var createDelegateExpression = (MethodCallExpression)unaryExpression.Operand;
var method = (MethodInfo)( (ConstantExpression)createDelegateExpression.Object ).Value;
var instance = Expression.Parameter( typeof( TOwner ), "instance" );
var oldValue = Expression.Parameter( typeof( TValue ), "oldValue" );
var newValue = Expression.Parameter( typeof( TValue ), "newValue" );
var methodCall = Expression.Call( instance, method, oldValue, newValue );
var callback = Expression.Lambda<Action<TOwner, TValue, TValue>>( methodCall, instance, oldValue, newValue ).Compile();
m_changeCallback = new PropertyChangedCallback<TOwner>(
( sender, e ) => callback( sender, (TValue)e.OldValue, (TValue)e.NewValue ) );
return this;
}
public Property<TValue> Coerce( CoerceValueCallback<TOwner, TValue> callback )
{
if ( typeof( TValue ).IsValueType )
{
// Create a callback that tries to avoid reboxing the original value if the coercion didn't change it
m_coerceCallback = ( sender, baseValue ) =>
{
var unboxedBaseValue = (TValue)baseValue;
var coercedValue = callback( (TOwner)sender, unboxedBaseValue );
if ( EqualityComparer<TValue>.Default.Equals( coercedValue, unboxedBaseValue ) )
{
// Avoid creating new boxed instance
return baseValue;
}
return KnownBoxes.Box( coercedValue );
};
}
else
{
m_coerceCallback = ( sender, args ) => callback( (TOwner)sender, (TValue)args );
}
return this;
}
public Property<TValue> CoerceObject( CoerceValueCallback<TOwner, Object> callback )
{
m_coerceCallback = ( sender, args ) => callback( (TOwner)sender, args );
return this;
}
public Property<TValue> Validate( ValidateValueCallback<TValue> callback )
{
m_validateCallback = callback;
return this;
}
public static implicit operator DependencyProperty( Property<TValue> instance )
{
var propertyName = instance.m_propertyName;
var validateCallback = instance.m_validateCallback;
return DependencyProperty.Register(
propertyName,
typeof( TValue ),
typeof( TOwner ),
instance.CreateMetadata(),
validateCallback != null ? new ValidateValueCallback( x => validateCallback( (TValue)x ) ) : null );
}
public static implicit operator DependencyPropertyKey( Property<TValue> instance )
{
var propertyName = instance.m_propertyName;
var validateCallback = instance.m_validateCallback;
return DependencyProperty.RegisterReadOnly(
propertyName,
typeof( TValue ),
typeof( TOwner ),
instance.CreateMetadata(),
validateCallback != null ? new ValidateValueCallback( x => validateCallback( (TValue)x ) ) : null );
}
private FrameworkPropertyMetadata CreateMetadata()
{
// Capture delegates
var changeCallback = m_changeCallback;
var coerceCallback = m_coerceCallback;
var metadata = new FrameworkPropertyMetadata( KnownBoxes.Box( m_default ), m_flags );
if ( changeCallback != null )
{
metadata.PropertyChangedCallback = new PropertyChangedCallback( ( sender, args ) => changeCallback( (TOwner)sender, args ) );
}
if ( coerceCallback != null )
{
metadata.CoerceValueCallback = coerceCallback;
}
if ( m_updateSourceTrigger != null )
{
metadata.DefaultUpdateSourceTrigger = m_updateSourceTrigger.Value;
}
return metadata;
}
private readonly String m_propertyName;
private TValue m_default;
private PropertyChangedCallback<TOwner> m_changeCallback;
private FrameworkPropertyMetadataOptions m_flags;
private ValidateValueCallback<TValue> m_validateCallback;
private CoerceValueCallback m_coerceCallback;
private UpdateSourceTrigger? m_updateSourceTrigger;
}
public sealed class AttachedProperty<TValue>
{
internal AttachedProperty( String propertyName )
{
m_propertyName = propertyName;
}
public AttachedProperty<TValue> Default( TValue value )
{
m_default = value;
return this;
}
public AttachedProperty<TValue> AffectsRender()
{
m_flags |= FrameworkPropertyMetadataOptions.AffectsRender;
return this;
}
public AttachedProperty<TValue> AffectsMeasure()
{
m_flags |= FrameworkPropertyMetadataOptions.AffectsMeasure;
return this;
}
public AttachedProperty<TValue> AffectsArrange()
{
m_flags |= FrameworkPropertyMetadataOptions.AffectsArrange;
return this;
}
public AttachedProperty<TValue> BindsTwoWayByDefault()
{
m_flags |= FrameworkPropertyMetadataOptions.BindsTwoWayByDefault;
return this;
}
public AttachedProperty<TValue> NotDataBindable()
{
m_flags |= FrameworkPropertyMetadataOptions.NotDataBindable;
return this;
}
public AttachedProperty<TValue> Inheritable()
{
m_flags |= FrameworkPropertyMetadataOptions.Inherits;
return this;
}
public AttachedProperty<TValue> Journal()
{
m_flags |= FrameworkPropertyMetadataOptions.Journal;
return this;
}
public AttachedProperty<TValue> UpdateSource( UpdateSourceTrigger trigger )
{
m_updateSourceTrigger = trigger;
return this;
}
public AttachedProperty<TValue> OnChange( PropertyChangedCallback callback )
{
m_changeCallback = callback;
return this;
}
public AttachedProperty<TValue> Coerce( CoerceValueCallback callback )
{
m_coerceCallback = callback;
return this;
}
public AttachedProperty<TValue> Validate( ValidateValueCallback callback )
{
m_validateCallback = callback;
return this;
}
public static implicit operator DependencyProperty( AttachedProperty<TValue> instance )
{
var propertyName = instance.m_propertyName;
var validateCallback = instance.m_validateCallback;
return DependencyProperty.RegisterAttached( propertyName,
typeof( TValue ),
typeof( TOwner ),
instance.CreateMetadata(),
validateCallback );
}
public static implicit operator DependencyPropertyKey( AttachedProperty<TValue> instance )
{
var propertyName = instance.m_propertyName;
var validateCallback = instance.m_validateCallback;
return DependencyProperty.RegisterAttachedReadOnly( propertyName,
typeof( TValue ),
typeof( TOwner ),
instance.CreateMetadata(),
validateCallback );
}
private FrameworkPropertyMetadata CreateMetadata()
{
var metadata = new FrameworkPropertyMetadata( KnownBoxes.Box( m_default ), m_flags )
{
PropertyChangedCallback = m_changeCallback,
CoerceValueCallback = m_coerceCallback
};
if ( m_updateSourceTrigger != null )
{
metadata.DefaultUpdateSourceTrigger = m_updateSourceTrigger.Value;
}
return metadata;
}
private readonly String m_propertyName;
private TValue m_default;
private PropertyChangedCallback m_changeCallback;
private FrameworkPropertyMetadataOptions m_flags;
private ValidateValueCallback m_validateCallback;
private CoerceValueCallback m_coerceCallback;
private UpdateSourceTrigger? m_updateSourceTrigger;
}
public static Type CurrentType => typeof( TOwner );
public static Property<TProperty> RegisterProperty<TProperty>( Expression<Func<TOwner, TProperty>> property )
{
if ( property == null )
{
throw new ArgumentNullException( nameof( property ) );
}
var propertyInfo = (PropertyInfo)( (MemberExpression)property.Body ).Member;
return new Property<TProperty>( propertyInfo.Name );
}
public static Property<TProperty> RegisterProperty<TProperty>( String propertyName = null, [CallerMemberName] String staticReadOnlyName = null )
{
if ( propertyName == null )
{
propertyName = staticReadOnlyName.Substring( 0, staticReadOnlyName.Length - "Property".Length );
}
else
{
Debug.Assert( propertyName + "Property" == staticReadOnlyName );
}
return new Property<TProperty>( propertyName );
}
public static AttachedProperty<TProperty> RegisterAttachedProperty<TProperty>( String propertyName = null, [CallerMemberName] String staticReadOnlyName = null )
{
if ( propertyName == null )
{
propertyName = staticReadOnlyName.Substring( 0, staticReadOnlyName.Length - "Property".Length );
}
else
{
Debug.Assert( propertyName + "Property" == staticReadOnlyName );
}
return new AttachedProperty<TProperty>( propertyName );
}
public static RoutedEvent RegisterEvent<THandler>( String eventName, RoutingStrategy strategy )
{
return EventManager.RegisterRoutedEvent( eventName, strategy, typeof( THandler ), CurrentType );
}
}
public delegate void PropertyChangedCallback<TOwner>( TOwner sender, DependencyPropertyChangedEventArgs e ) where TOwner : DependencyObject;
public delegate Boolean ValidateValueCallback<TValue>( TValue value );
public delegate TValue CoerceValueCallback<TOwner, TValue>( TOwner sender, TValue value );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment