Обновлено 2018 для Bootstrap 4.0.0
Переходы анимации Bootstrap 4 используют transform
, поэтому я думаю, что было бы лучше использовать translate
для изменения ориентации по вертикали и положению каждого слайда ...
Демо: http://www.codeply.com/go/PgxKT3h6x6
.vert .carousel-item-next.carousel-item-left,
.vert .carousel-item-prev.carousel-item-right {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
.vert .carousel-item-next,
.vert .active.carousel-item-right {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100% 0);
}
.vert .carousel-item-prev,
.vert .active.carousel-item-left {
-webkit-transform: translate3d(0,-100%, 0);
transform: translate3d(0,-100%, 0);
}
Не используя что-то вроде postsharp, минимальная версия, которую я использую, использует что-то вроде:
public class Data : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
// props
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
}
Каждое свойство - это просто что-то вроде:
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
, который не является огромный; его также можно использовать в качестве базового класса, если хотите. Возврат bool
из SetField
говорит вам, был ли он нерабочим, если вы хотите применить другую логику.
или даже проще с C # 5:
protected bool SetField<T>(ref T field, T value,
[CallerMemberName] string propertyName = null)
{...}
, который можно вызвать следующим образом:
set { SetField(ref name, value); }
, с которым компилятор автоматически добавит "Name"
.
C # 6.0 упрощает реализацию :
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
... и теперь с C # 7:
private string name;
public string Name
{
get => name;
set => SetField(ref name, value);
}
=> здесь мое решение со следующими признаками
public ResourceStatus Status
{
get { return _status; }
set
{
_status = value;
Notify(Npcea.Status,Npcea.Comments);
}
}
protected T Get<T>(T defaultValue, [CallerMemberName] string name = null)
, а также проверить if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name)))
в Set (для повышения и сохранения при первом задании значения по умолчанию)
– Miquel
12 February 2015 в 03:35
Посмотрите здесь: http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx
Это написано на немецком языке, но вы можете загрузить ViewModelBase.cs. Все комментарии в cs-файле написаны на английском языке.
С помощью этого ViewModelBase-Class можно реализовать свойства связывания, аналогичные хорошо известным свойствам зависимостей:
public string SomeProperty
{
get { return GetValue( () => SomeProperty ); }
set { SetValue( () => SomeProperty, value ); }
}
Очень похожий на AOP подход - это вводить материал INotifyPropertyChanged на уже созданный объект на лету. Вы можете сделать это с помощью чего-то вроде Castle DynamicProxy. Вот статья, которая объясняет технику:
Да, лучший способ, безусловно, существует. Вот он:
Пошаговое руководство сократилось мной на основе этой полезной статьи .
Install-Package Castle.Core
- Установить только библиотеки mvvm light
Install-Package MvvmLightLibs
- Добавить два класса в проект:
NotifierInterceptor g15]
public class NotifierInterceptor : IInterceptor { private PropertyChangedEventHandler handler; public static Dictionary<String, PropertyChangedEventArgs> _cache = new Dictionary<string, PropertyChangedEventArgs>(); public void Intercept(IInvocation invocation) { switch (invocation.Method.Name) { case "add_PropertyChanged": handler = (PropertyChangedEventHandler) Delegate.Combine(handler, (Delegate)invocation.Arguments[0]); invocation.ReturnValue = handler; break; case "remove_PropertyChanged": handler = (PropertyChangedEventHandler) Delegate.Remove(handler, (Delegate)invocation.Arguments[0]); invocation.ReturnValue = handler; break; default: if (invocation.Method.Name.StartsWith("set_")) { invocation.Proceed(); if (handler != null) { var arg = retrievePropertyChangedArg(invocation.Method.Name); handler(invocation.Proxy, arg); } } else invocation.Proceed(); break; } } private static PropertyChangedEventArgs retrievePropertyChangedArg(String methodName) { PropertyChangedEventArgs arg = null; _cache.TryGetValue(methodName, out arg); if (arg == null) { arg = new PropertyChangedEventArgs(methodName.Substring(4)); _cache.Add(methodName, arg); } return arg; } }
ProxyCreator
public class ProxyCreator { public static T MakeINotifyPropertyChanged<T>() where T : class, new() { var proxyGen = new ProxyGenerator(); var proxy = proxyGen.CreateClassProxy( typeof(T), new[] { typeof(INotifyPropertyChanged) }, ProxyGenerationOptions.Default, new NotifierInterceptor() ); return proxy as T; } }
- Создайте свою модель просмотра, например:
-
public class MainViewModel { public virtual string MainTextBox { get; set; } public RelayCommand TestActionCommand { get { return new RelayCommand(TestAction); } } public void TestAction() { Trace.WriteLine(MainTextBox); } }
- Поместите привязки в xaml:
<TextBox Text="{Binding MainTextBox}" ></TextBox> <Button Command="{Binding TestActionCommand}" >Test</Button>
- Поместите строку кода в файл с кодом ниже MainWindow.xaml.cs следующим образом:
DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();
- Наслаждайтесь.
[/g2]
Внимание !!! Все ограниченные свойства должны быть украшены ключевыми словами virtual, потому что они используются прокси-сервером замка для переопределения.
type
, interfaces to apply
, interceptors
.
– IAbstract
13 April 2016 в 21:56
CreateClassProxy<T>
. Многое ... Хммм, интересно, почему так ограниченный общий метод. :(
– IAbstract
13 April 2016 в 22:25
Я использую следующий метод расширения (используя C # 6.0), чтобы сделать реализацию INPC максимально простой:
public static bool ChangeProperty<T>(this PropertyChangedEventHandler propertyChanged, ref T field, T value, object sender,
IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
if (comparer == null)
comparer = EqualityComparer<T>.Default;
if (comparer.Equals(field, value))
{
return false;
}
else
{
field = value;
propertyChanged?.Invoke(sender, new PropertyChangedEventArgs(propertyName));
return true;
}
}
Реализация INPC сводится к (вы можете либо реализовать это каждый раз, либо создать базовый класс):
public class INPCBaseClass: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool changeProperty<T>(ref T field, T value,
IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
return PropertyChanged.ChangeProperty(ref field, value, this, comparer, propertyName);
}
}
Затем напишите ваши свойства следующим образом:
private string testProperty;
public string TestProperty
{
get { return testProperty; }
set { changeProperty(ref testProperty, value); }
}
ПРИМЕЧАНИЕ. Вы можете опустить объявление [CallerMemberName]
в методе расширения, если хотите , но я хотел сохранить его гибким.
Если у вас есть свойства без поля поддержки, вы можете перегрузить changeProperty
:
protected bool changeProperty<T>(T property, Action<T> set, T value,
IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
bool ret = changeProperty(ref property, value, comparer, propertyName);
if (ret)
set(property);
return ret;
}
Пример использования:
public string MyTestProperty
{
get { return base.TestProperty; }
set { changeProperty(base.TestProperty, (x) => { base.TestProperty = x; }, value); }
}
Я думаю, что люди должны уделять немного больше внимания производительности, это действительно влияет на пользовательский интерфейс, когда есть много объектов, которые нужно связывать (подумайте о сетке с 10 000 + строками) или если значение объекта изменяется часто (в реальном времени приложение мониторинга).
Я взял различную реализацию, найденную здесь и в другом месте, и сделал сравнение, проверил это сравнение производительности реализаций INotifyPropertyChanged .
Вот загляните в результат [/g1]
.Net 4.5 вводит новые атрибуты информации о вызывающем абоненте.
private void OnPropertyChanged<T>([CallerMemberName]string caller = null) {
// make sure only to call this if the value actually changes
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(caller));
}
}
Это, вероятно, хорошая идея для добавления компаратора к функции.
EqualityComparer<T>.Default.Equals
Здесь приведены примеры здесь и здесь
Также см. Информация о вызывающем абоненте (C # и Visual Basic)
Мне очень нравится решение Marc, но я думаю, что его можно немного улучшить, чтобы избежать использования «волшебной строки» (которая не поддерживает рефакторинг). Вместо использования имени свойства в виде строки легко сделать это лямбда-выражением:
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, () => Name); }
}
Просто добавьте следующие методы в код Marc, он выполнит трюк:
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
if (selectorExpression == null)
throw new ArgumentNullException("selectorExpression");
MemberExpression body = selectorExpression.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
OnPropertyChanged(body.Member.Name);
}
protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(selectorExpression);
return true;
}
BTW, это было вдохновлено этим сообщением в блоге обновленным URL
Позвольте мне представить свой подход под названием Yappi . Он относится к генераторам производного класса Runtime proxy, добавляя новые функции к существующему объекту или типу, например Dynamic Proxy от Caste Project.
Он позволяет реализовать INotifyPropertyChanged один раз в базовом классе, а затем объявлять производные классы в следующих стиль, поддерживающий INotifyPropertyChanged для новых свойств:
public class Animal:Concept
{
protected Animal(){}
public virtual string Name { get; set; }
public virtual int Age { get; set; }
}
Сложность построения производного класса или прокси может быть скрыта за следующей строкой:
var animal = Concept.Create<Animal>.New();
И вся работа по реализации INotifyPropertyChanged может сделайте это следующим образом:
public class Concept:INotifyPropertyChanged
{
//Hide constructor
protected Concept(){}
public static class Create<TConcept> where TConcept:Concept
{
//Construct derived Type calling PropertyProxy.ConstructType
public static readonly Type Type = PropertyProxy.ConstructType<TConcept, Implementation<TConcept>>(new Type[0], true);
//Create constructing delegate calling Constructor.Compile
public static Func<TConcept> New = Constructor.Compile<Func<TConcept>>(Type);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
{
var caller = PropertyChanged;
if(caller!=null)
{
caller(this, eventArgs);
}
}
//define implementation
public class Implementation<TConcept> : DefaultImplementation<TConcept> where TConcept:Concept
{
public override Func<TBaseType, TResult> OverrideGetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
{
return PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);
}
/// <summary>
/// Overriding property setter implementation.
/// </summary>
/// <typeparam name="TBaseType">Base type for implementation. TBaseType must be TConcept, and inherits all its constraints. Also TBaseType is TDeclaringType.</typeparam>
/// <typeparam name="TDeclaringType">Type, declaring property.</typeparam>
/// <typeparam name="TConstructedType">Constructed type. TConstructedType is TDeclaringType and TBaseType.</typeparam>
/// <typeparam name="TResult">Type of property.</typeparam>
/// <param name="property">PropertyInfo of property.</param>
/// <returns>Delegate, corresponding to property setter implementation.</returns>
public override Action<TBaseType, TResult> OverrideSetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
{
//This code called once for each declared property on derived type's initialization.
//EventArgs instance is shared between all events for each concrete property.
var eventArgs = new PropertyChangedEventArgs(property.Name);
//get delegates for base calls.
Action<TBaseType, TResult> setter = PropertyImplementation<TBaseType, TDeclaringType>.GetSetter<TResult>(property.Name);
Func<TBaseType, TResult> getter = PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);
var comparer = EqualityComparer<TResult>.Default;
return (pthis, value) =>
{//This code executes each time property setter is called.
if (comparer.Equals(value, getter(pthis))) return;
//base. call
setter(pthis, value);
//Directly accessing Concept's protected method.
pthis.OnPropertyChanged(eventArgs);
};
}
}
}
Он полностью безопасен для рефакторинга, не использует отражения после построения типа и достаточно быстро.
TDeclaration
на PropertyImplementation
? Разумеется, вы можете найти подходящий тип для вызова (не callvirt) получателя / сеттера с помощью только TImplementation
?
– Andrew Savinykh
22 June 2015 в 03:13
Если вы используете динамику в .NET 4.5, вам не нужно беспокоиться о INotifyPropertyChanged
.
dynamic obj = new ExpandoObject();
obj.Name = "John";
, если имя связано с каким-то контролем, оно просто отлично работает.
Используйте это
using System;
using System.ComponentModel;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
public static class ObservableFactory
{
public static T Create<T>(T target)
{
if (!typeof(T).IsInterface)
throw new ArgumentException("Target should be an interface", "target");
var proxy = new Observable<T>(target);
return (T)proxy.GetTransparentProxy();
}
}
internal class Observable<T> : RealProxy, INotifyPropertyChanged, INotifyPropertyChanging
{
private readonly T target;
internal Observable(T target)
: base(ImplementINotify(typeof(T)))
{
this.target = target;
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
if (methodCall != null)
{
return HandleMethodCall(methodCall);
}
return null;
}
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
IMessage HandleMethodCall(IMethodCallMessage methodCall)
{
var isPropertySetterCall = methodCall.MethodName.StartsWith("set_");
var propertyName = isPropertySetterCall ? methodCall.MethodName.Substring(4) : null;
if (isPropertySetterCall)
{
OnPropertyChanging(propertyName);
}
try
{
object methodCalltarget = target;
if (methodCall.MethodName == "add_PropertyChanged" || methodCall.MethodName == "remove_PropertyChanged"||
methodCall.MethodName == "add_PropertyChanging" || methodCall.MethodName == "remove_PropertyChanging")
{
methodCalltarget = this;
}
var result = methodCall.MethodBase.Invoke(methodCalltarget, methodCall.InArgs);
if (isPropertySetterCall)
{
OnPropertyChanged(methodCall.MethodName.Substring(4));
}
return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
}
catch (TargetInvocationException invocationException)
{
var exception = invocationException.InnerException;
return new ReturnMessage(exception, methodCall);
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanging(string propertyName)
{
var handler = PropertyChanging;
if (handler != null) handler(this, new PropertyChangingEventArgs(propertyName));
}
public static Type ImplementINotify(Type objectType)
{
var tempAssemblyName = new AssemblyName(Guid.NewGuid().ToString());
var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
tempAssemblyName, AssemblyBuilderAccess.RunAndCollect);
var moduleBuilder = dynamicAssembly.DefineDynamicModule(
tempAssemblyName.Name,
tempAssemblyName + ".dll");
var typeBuilder = moduleBuilder.DefineType(
objectType.FullName, TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract);
typeBuilder.AddInterfaceImplementation(objectType);
typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged));
typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanging));
var newType = typeBuilder.CreateType();
return newType;
}
}
}
Я создал метод расширения в моей базовой библиотеке для повторного использования:
public static class INotifyPropertyChangedExtensions
{
public static bool SetPropertyAndNotify<T>(this INotifyPropertyChanged sender,
PropertyChangedEventHandler handler, ref T field, T value,
[CallerMemberName] string propertyName = "",
EqualityComparer<T> equalityComparer = null)
{
bool rtn = false;
var eqComp = equalityComparer ?? EqualityComparer<T>.Default;
if (!eqComp.Equals(field,value))
{
field = value;
rtn = true;
if (handler != null)
{
var args = new PropertyChangedEventArgs(propertyName);
handler(sender, args);
}
}
return rtn;
}
}
Это работает с .Net 4.5 из-за CallerMemberNameAttribute . Если вы хотите использовать его с более ранней версией .Net, вам нужно изменить объявление метода с: ...,[CallerMemberName] string propertyName = "", ...
до ...,string propertyName, ...
Использование:
public class Dog : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string _name;
public string Name
{
get { return _name; }
set
{
this.SetPropertyAndNotify(PropertyChanged, ref _name, value);
}
}
}
Я придумал этот базовый класс для реализации наблюдаемого шаблона, в значительной степени делает то, что вам нужно ( «автоматически» , реализующий набор и get). Я потратил на это час в качестве прототипа, поэтому он не имеет много модульных тестов, но доказывает концепцию. Обратите внимание, что для удаления необходимости в частных полях используется Dictionary<string, ObservablePropertyContext>
.
public class ObservableByTracking<T> : IObservable<T>
{
private readonly Dictionary<string, ObservablePropertyContext> _expando;
private bool _isDirty;
public ObservableByTracking()
{
_expando = new Dictionary<string, ObservablePropertyContext>();
var properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
foreach (var property in properties)
{
var valueContext = new ObservablePropertyContext(property.Name, property.PropertyType)
{
Value = GetDefault(property.PropertyType)
};
_expando[BuildKey(valueContext)] = valueContext;
}
}
protected void SetValue<T>(Expression<Func<T>> expression, T value)
{
var keyContext = GetKeyContext(expression);
var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);
if (!_expando.ContainsKey(key))
{
throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
}
var originalValue = (T)_expando[key].Value;
if (EqualityComparer<T>.Default.Equals(originalValue, value))
{
return;
}
_expando[key].Value = value;
_isDirty = true;
}
protected T GetValue<T>(Expression<Func<T>> expression)
{
var keyContext = GetKeyContext(expression);
var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);
if (!_expando.ContainsKey(key))
{
throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
}
var value = _expando[key].Value;
return (T)value;
}
private KeyContext GetKeyContext<T>(Expression<Func<T>> expression)
{
var castedExpression = expression.Body as MemberExpression;
if (castedExpression == null)
{
throw new Exception($"Invalid expression.");
}
var parameterName = castedExpression.Member.Name;
var propertyInfo = castedExpression.Member as PropertyInfo;
if (propertyInfo == null)
{
throw new Exception($"Invalid expression.");
}
return new KeyContext {PropertyType = propertyInfo.PropertyType, PropertyName = parameterName};
}
private static string BuildKey(ObservablePropertyContext observablePropertyContext)
{
return $"{observablePropertyContext.Type.Name}.{observablePropertyContext.Name}";
}
private static string BuildKey(string parameterName, Type type)
{
return $"{type.Name}.{parameterName}";
}
private static object GetDefault(Type type)
{
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}
public bool IsDirty()
{
return _isDirty;
}
public void SetPristine()
{
_isDirty = false;
}
private class KeyContext
{
public string PropertyName { get; set; }
public Type PropertyType { get; set; }
}
}
public interface IObservable<T>
{
bool IsDirty();
void SetPristine();
}
Вот использование
public class ObservableByTrackingTestClass : ObservableByTracking<ObservableByTrackingTestClass>
{
public ObservableByTrackingTestClass()
{
StringList = new List<string>();
StringIList = new List<string>();
NestedCollection = new List<ObservableByTrackingTestClass>();
}
public IEnumerable<string> StringList
{
get { return GetValue(() => StringList); }
set { SetValue(() => StringIList, value); }
}
public IList<string> StringIList
{
get { return GetValue(() => StringIList); }
set { SetValue(() => StringIList, value); }
}
public int IntProperty
{
get { return GetValue(() => IntProperty); }
set { SetValue(() => IntProperty, value); }
}
public ObservableByTrackingTestClass NestedChild
{
get { return GetValue(() => NestedChild); }
set { SetValue(() => NestedChild, value); }
}
public IList<ObservableByTrackingTestClass> NestedCollection
{
get { return GetValue(() => NestedCollection); }
set { SetValue(() => NestedCollection, value); }
}
public string StringProperty
{
get { return GetValue(() => StringProperty); }
set { SetValue(() => StringProperty, value); }
}
}
Я только что нашел ActiveSharp - Automatic INotifyPropertyChanged , я еще не использовал его, но он выглядит хорошо.
Чтобы процитировать его на веб-сайте ...
Отправить уведомления об изменении свойств без указания имени свойства как строки.
Вместо этого напишите свойства следующим образом:
public int Foo { get { return _foo; } set { SetValue(ref _foo, value); } // <-- no property name here }
Обратите внимание, что нет необходимости включать имя свойства в виде строки. ActiveSharp надежно и правильно определяет это для себя. Он работает на основе того факта, что ваша реализация свойства передает поле поддержки (_foo) по ссылке. (ActiveSharp использует этот вызов «ref» для определения того, какое поле поддержки было передано, и из поля оно идентифицирует свойство).
Идея с использованием отражения:
class ViewModelBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
bool Notify<T>(MethodBase mb, ref T oldValue, T newValue) {
// Get Name of Property
string name = mb.Name.Substring(4);
// Detect Change
bool changed = EqualityComparer<T>.Default.Equals(oldValue, newValue);
// Return if no change
if (!changed) return false;
// Update value
oldValue = newValue;
// Raise Event
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(name));
}//if
// Notify caller of change
return true;
}//method
string name;
public string Name {
get { return name; }
set {
Notify(MethodInfo.GetCurrentMethod(), ref this.name, value);
}
}//method
}//class
Я понимаю, что этот вопрос уже имеет ответы, но никто из них не чувствовал себя вполне подходящим для меня. Моя проблема в том, что я не хочу никаких хитов производительности, и я готов смириться с небольшой подробностью только по этой причине. Я также не слишком забочусь об авто свойствах, что привело меня к следующему решению:
public abstract class AbstractObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetValue<TKind>(ref TKind Source, TKind NewValue, params string[] Notify)
{
//Set value if the new value is different from the old
if (!Source.Equals(NewValue))
{
Source = NewValue;
//Notify all applicable properties
foreach (var i in Notify)
OnPropertyChanged(i);
return true;
}
return false;
}
public AbstractObject()
{
}
}
Другими словами, вышеупомянутое решение удобно, если вы не возражаете против этого:
public class SomeObject : AbstractObject
{
public string AnotherProperty
{
get
{
return someProperty ? "Car" : "Plane";
}
}
bool someProperty = false;
public bool SomeProperty
{
get
{
return someProperty;
}
set
{
SetValue(ref someProperty, value, "SomeProperty", "AnotherProperty");
}
}
public SomeObject() : base()
{
}
}
Плюсы
Минусы
Увы, все же лучше, чем делать это,
set
{
if (!someProperty.Equals(value))
{
someProperty = value;
OnPropertyChanged("SomeProperty");
OnPropertyChanged("AnotherProperty");
}
}
Для каждого отдельного имущества, которое становится кошмаром с Дополнительная информация: - (
Обратите внимание: я не утверждаю, что это решение лучше по производительности по сравнению с другими, просто для того, чтобы быть жизнеспособным решением для тех, кому не нравятся другие представленные решения.
Призма 5:
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual bool SetProperty<T>(ref T storage,
T value,
[CallerMemberName] string propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged(string propertyName)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
protected void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
var propertyName = PropertySupport.ExtractPropertyName(propertyExpression);
this.OnPropertyChanged(propertyName);
}
}
public static class PropertySupport
{
public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
{
if (propertyExpression == null)
{
throw new ArgumentNullException("propertyExpression");
}
var memberExpression = propertyExpression.Body as MemberExpression;
if (memberExpression == null)
{
throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
}
var property = memberExpression.Member as PropertyInfo;
if (property == null)
{
throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
}
var getMethod = property.GetMethod;
if (getMethod.IsStatic)
{
throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
}
return memberExpression.Member.Name;
}
}
Я решил на этом пути (это немного работает, но это, безусловно, быстрее во время исполнения).
В VB (извините, но я думаю, что это не сложно перевести его на C #), я делаю эта подстановка с RE:
(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)( |\r\n)*(?<Def>(Public|Private|Friend|Protected) .*Property )(?<Name>[^ ]*) As (?<Type>.*?)[ |\r\n](?![ |\r\n]*Get)
с:
Private _${Name} As ${Type}\r\n${Attr}\r\n${Def}${Name} As ${Type}\r\nGet\r\nReturn _${Name}\r\nEnd Get\r\nSet (Value As ${Type})\r\nIf _${Name} <> Value Then \r\n_${Name} = Value\r\nRaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("${Name}"))\r\nEnd If\r\nEnd Set\r\nEnd Property\r\n
Это переводит весь код следующим образом:
<Bindable(True)>
Protected Friend Property StartDate As DateTime?
In
Private _StartDate As DateTime?
<Bindable(True)>
Protected Friend Property StartDate As DateTime?
Get
Return _StartDate
End Get
Set(Value As DateTime?)
If _StartDate <> Value Then
_StartDate = Value
RaiseEvent PropertyChange(Me, New ComponentModel.PropertyChangedEventArgs("StartDate"))
End If
End Set
End Property
И если я хочу иметь более читаемый код, я могу сделать обратное, просто сделав следующую замену:
Private _(?<Name>.*) As (?<Type>.*)[\r\n ]*(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)[\r\n ]*(?<Def>(Public|Private|Friend|Protected) .*Property )\k<Name> As \k<Type>[\r\n ]*Get[\r\n ]*Return _\k<Name>[\r\n ]*End Get[\r\n ]*Set\(Value As \k<Type>\)[\r\n ]*If _\k<Name> <> Value Then[\r\n ]*_\k<Name> = Value[\r\n ]*RaiseEvent PropertyChanged\(Me, New (.*ComponentModel\.)PropertyChangedEventArgs\("\k<Name>"\)\)[\r\n ]*End If[\r\n ]*End Set[\r\n ]*End Property
С
${Attr} ${Def} ${Name} As ${Type}
я бросаю чтобы заменить IL-код установленного метода, но я не могу написать много скомпилированного кода в IL ... Если в день я его напишу, я скажу вам!
Я написал статью, которая помогает с этим ( https://msdn.microsoft.com/magazine/mt736453 ). Вы можете использовать пакет SolSoft.DataBinding NuGet. Затем вы можете написать такой код:
public class TestViewModel : IRaisePropertyChanged
{
public TestViewModel()
{
this.m_nameProperty = new NotifyProperty<string>(this, nameof(Name), null);
}
private readonly NotifyProperty<string> m_nameProperty;
public string Name
{
get
{
return m_nameProperty.Value;
}
set
{
m_nameProperty.SetValue(value);
}
}
// Plus implement IRaisePropertyChanged (or extend BaseViewModel)
}
Преимущества:
Я сохраняю это как фрагмент. C # 6 добавляет хороший синтаксис для вызова обработчика.
// INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void Set<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(property, value) == false)
{
property = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Другое комбинированное решение использует StackFrame:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T field, T value)
{
MethodBase method = new StackFrame(1).GetMethod();
field = value;
Raise(method.Name.Substring(4));
}
protected void Raise(string propertyName)
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Использование:
public class TempVM : BaseViewModel
{
private int _intP;
public int IntP
{
get { return _intP; }
set { Set<int>(ref _intP, value); }
}
}
Другие вещи, которые вы, возможно, захотите рассмотреть при реализации этих свойств, это тот факт, что INotifyPropertyChang * ed * ing используют классы аргументов аргументов.
Если у вас есть большое количество свойств, которые set, то количество экземпляров класса аргументов событий может быть огромным, вы должны рассмотреть их кеширование, поскольку они являются одной из областей, где может произойти взрыв строки.
Взгляните на эту реализацию и объясните, почему это был задуман.
Вот версия Unity3D или non-CallerMemberName NotifyPropertyChanged
public abstract class Bindable : MonoBehaviour, INotifyPropertyChanged
{
private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
private static readonly StackTrace stackTrace = new StackTrace();
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Resolves a Property's name from a Lambda Expression passed in.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="property"></param>
/// <returns></returns>
internal string GetPropertyName<T>(Expression<Func<T>> property)
{
var expression = (MemberExpression) property.Body;
var propertyName = expression.Member.Name;
Debug.AssertFormat(propertyName != null, "Bindable Property shouldn't be null!");
return propertyName;
}
#region Notification Handlers
/// <summary>
/// Notify's all other objects listening that a value has changed for nominated propertyName
/// </summary>
/// <param name="propertyName"></param>
internal void NotifyOfPropertyChange(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Notifies subscribers of the property change.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="property">The property expression.</param>
internal void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
{
var propertyName = GetPropertyName(property);
NotifyOfPropertyChange(propertyName);
}
/// <summary>
/// Raises the <see cref="PropertyChanged" /> event directly.
/// </summary>
/// <param name="e">The <see cref="PropertyChangedEventArgs" /> instance containing the event data.</param>
internal void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
#endregion
#region Getters
/// <summary>
/// Gets the value of a property
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
internal T Get<T>(Expression<Func<T>> property)
{
var propertyName = GetPropertyName(property);
return Get<T>(GetPropertyName(property));
}
/// <summary>
/// Gets the value of a property automatically based on its caller.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
internal T Get<T>()
{
var name = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
return Get<T>(name);
}
/// <summary>
/// Gets the name of a property based on a string.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
internal T Get<T>(string name)
{
object value = null;
if (_properties.TryGetValue(name, out value))
return value == null ? default(T) : (T) value;
return default(T);
}
#endregion
#region Setters
/// <summary>
/// Sets the value of a property whilst automatically looking up its caller name.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
internal void Set<T>(T value)
{
var propertyName = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
Set(value, propertyName);
}
/// <summary>
/// Sets the value of a property
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="name"></param>
internal void Set<T>(T value, string propertyName)
{
Debug.Assert(propertyName != null, "name != null");
if (Equals(value, Get<T>(propertyName)))
return;
_properties[propertyName] = value;
NotifyOfPropertyChange(propertyName);
}
/// <summary>
/// Sets the value of a property based off an Expression (()=>FieldName)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="property"></param>
internal void Set<T>(T value, Expression<Func<T>> property)
{
var propertyName = GetPropertyName(property);
Debug.Assert(propertyName != null, "name != null");
if (Equals(value, Get<T>(propertyName)))
return;
_properties[propertyName] = value;
NotifyOfPropertyChange(propertyName);
}
#endregion
}
. Этот код позволяет вам создавать поля поддержки свойств следующим образом:
public string Text
{
get { return Get<string>(); }
set { Set(value); }
}
Кроме того, в resharper если вы создаете фрагмент шаблона / поиска, вы также можете автоматизировать свой рабочий процесс, преобразовывая простые прокси-поля в вышеупомянутую поддержку.
Поиск:
public $type$ $fname$ { get; set; }
Заменить шаблон:
public $type$ $fname$
{
get { return Get<$type$>(); }
set { Set(value); }
}
Также есть Fody , который имеет надстройку PropertyChanged , которая позволяет вам записать это:
[ImplementPropertyChanged]
public class Person
{
public string GivenNames { get; set; }
public string FamilyName { get; set; }
}
... и при компиляции время вводит измененные изменения свойств.
"Fody/.*?:",LogCustom2,True
выделяет его в «Custom 2», цвет. Я сделал его ярко-розовым, поэтому его легко найти. Просто все, что угодно, это самый простой способ сделать что-нибудь, у которого много повторяющегося набора текста.
– CAD bloke
25 July 2017 в 04:28
Другая идея ...
public class ViewModelBase : INotifyPropertyChanged
{
private Dictionary<string, object> _propertyStore = new Dictionary<string, object>();
protected virtual void SetValue<T>(T value, [CallerMemberName] string propertyName="") {
_propertyStore[propertyName] = value;
OnPropertyChanged(propertyName);
}
protected virtual T GetValue<T>([CallerMemberName] string propertyName = "")
{
object ret;
if (_propertyStore.TryGetValue(propertyName, out ret))
{
return (T)ret;
}
else
{
return default(T);
}
}
//Usage
//public string SomeProperty {
// get { return GetValue<string>(); }
// set { SetValue(value); }
//}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var temp = PropertyChanged;
if (temp != null)
temp.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
protected T Get<T>(T defaultValue, [CallerMemberName] string name = null)
, а также проверить if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name)))
в Set (для повышения и сохранения при первом задании значения по умолчанию)
– Miquel
12 February 2015 в 03:35
Все эти ответы очень приятные.
Мое решение использует фрагменты кода для выполнения задания.
Это использует простейший вызов события PropertyChanged.
Сохраните этот фрагмент и используйте его, когда вы используете фрагмент «fullprop».
Местоположение можно найти в меню «Инструменты \ Код Snippet Manager ...» в Visual Studio.
blockquote><?xml version="1.0" encoding="utf-8" ?> <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <CodeSnippet Format="1.0.0"> <Header> <Title>inotifypropfull</Title> <Shortcut>inotifypropfull</Shortcut> <HelpUrl>http://ofirzeitoun.wordpress.com/</HelpUrl> <Description>Code snippet for property and backing field with notification</Description> <Author>Ofir Zeitoun</Author> <SnippetTypes> <SnippetType>Expansion</SnippetType> </SnippetTypes> </Header> <Snippet> <Declarations> <Literal> <ID>type</ID> <ToolTip>Property type</ToolTip> <Default>int</Default> </Literal> <Literal> <ID>property</ID> <ToolTip>Property name</ToolTip> <Default>MyProperty</Default> </Literal> <Literal> <ID>field</ID> <ToolTip>The variable backing this property</ToolTip> <Default>myVar</Default> </Literal> </Declarations> <Code Language="csharp"> <![CDATA[private $type$ $field$; public $type$ $property$ { get { return $field$;} set { $field$ = value; var temp = PropertyChanged; if (temp != null) { temp(this, new PropertyChangedEventArgs("$property$")); } } } $end$]]> </Code> </Snippet> </CodeSnippet> </CodeSnippets>
Вы можете изменить вызов по своему усмотрению (использовать вышеупомянутые решения)
[CallerMemberName]
– Marc Gravell♦ 22 October 2013 в 09:30