6

Caliburn.Micro 是否具有与Catel 的 [ExposeAttribute]类似的功能?

有没有其他方法可以减轻 Caliburn.Micro 中传递属性的工作?(即模型中的属性以及 ViewModel 中的属性,以允许 View 访问属性。)

4

1 回答 1

7

定义ExposeAttribute

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class ExposeAttribute : Attribute
{
    public ExposeAttribute(string propertyName)
    {
        PropertyName = propertyName;
    }

    public ExposeAttribute(string propertyName, string modelPropertyName)
    {
        PropertyName = propertyName;
        ModelPropertyName = modelPropertyName;
    }

    public string PropertyName { get; set; }

    public string ModelPropertyName { get; set; }
}

并使用ExposedPropertyBinder我刚刚为你写的这个:)

public static class ExposedPropertyBinder
{
    private static readonly ILog Log = LogManager.GetLog(typeof(ExposedPropertyBinder));

    public static void BindElements(IEnumerable<FrameworkElement> elements, Type viewModelType)
    {
        foreach (var element in elements)
        {
            var parts = element.Name.Trim('_')
                .Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries);

            // Get first exposed property
            var exposedPropertyInfo = GetExposedPropertyInfo(viewModelType, parts[0]);
            if (exposedPropertyInfo == null)
            {
                Log.Info("Binding Convention Not Applied: Element {0} did not match a property.", element.Name);
                continue;
            }

            var breadCrumb = new List<string> { exposedPropertyInfo.Path };

            // Loop over all parts and get exposed properties
            for (var i = 1; i < parts.Length; i++)
            {
                var exposedViewModelType = exposedPropertyInfo.ViewModelType;

                exposedPropertyInfo = GetExposedPropertyInfo(exposedViewModelType, parts[i]);
                if (exposedPropertyInfo == null) break;

                breadCrumb.Add(exposedPropertyInfo.Path);
            }

            if (exposedPropertyInfo == null)
            {
                Log.Info("Binding Convention Not Applied: Element {0} did not match a property.", element.Name);
                continue;
            }

            var convention = ConventionManager.GetElementConvention(element.GetType());
            if (convention == null)
            {
                Log.Warn("Binding Convention Not Applied: No conventions configured for {0}.", element.GetType());
                continue;
            }

            var applied = convention.ApplyBinding(exposedPropertyInfo.ViewModelType,
                string.Join(".", breadCrumb), exposedPropertyInfo.Property, element, convention);

            var appliedMessage = string.Format(applied 
                ? "Binding Convention Applied: Element {0}." 
                : "Binding Convention Not Applied: Element {0} has existing binding.", element.Name);

            Log.Info(appliedMessage);
        }
    }

    private static ExposedPropertyInfo GetExposedPropertyInfo(Type type, string propertyName)
    {
        foreach (var property in type.GetProperties())
        {
            if (property.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase))
                return new ExposedPropertyInfo(property.PropertyType, property.Name, property);

            // Get first ExposeAttribute which matches property name
            var exposeAttribute = GetExposeAttribute(property, propertyName);
            if (exposeAttribute == null) continue;

            // Get the name of the exposed property
            var exposedPropertyName = exposeAttribute.ModelPropertyName ?? exposeAttribute.PropertyName;

            var path = string.Join(".", property.Name, exposedPropertyName);
            var viewModelType = property.PropertyType;
            var propertyInfo = property;

            // Check if property exists
            var exposedProperty = viewModelType.GetPropertyCaseInsensitive(exposedPropertyName);
            if (exposedProperty == null)
            {
                // Do recursive check for exposed properties
                var child = GetExposedPropertyInfo(viewModelType, exposedPropertyName);
                if (child == null) continue;

                path = string.Join(".", property.Name, child.Path);
                viewModelType = child.ViewModelType;
                propertyInfo = child.Property;
            }

            return new ExposedPropertyInfo(viewModelType, path, propertyInfo);
        }

        return null;
    }

    private static ExposeAttribute GetExposeAttribute(PropertyInfo property, string propertyName)
    {
        return property
            .GetCustomAttributes(typeof(ExposeAttribute), true)
            .Cast<ExposeAttribute>()
            .FirstOrDefault(a => a.PropertyName.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
    }

    private class ExposedPropertyInfo
    {
        public ExposedPropertyInfo(Type viewModelType, string path, PropertyInfo property)
        {
            ViewModelType = viewModelType;
            Path = path;
            Property = property;
        }

        public Type ViewModelType { get; private set; }

        public string Path { get; private set; }

        public PropertyInfo Property { get; private set; }
    }
}

像这样将它连接到 Caliburn.Micro ViewModelBinder

ViewModelBinder.HandleUnmatchedElements = ExposedPropertyBinder.BindElements;

瞧!

使用以下内容装饰您的 ViewModel 属性ExposeAttribute

public class MainViewModel : PropertyChangedBase
{
    private Person _person;

    [Expose("FirstName")]
    [Expose("LastName")]
    [Expose("ZipCode")]
    public Person Person
    {
        get { return _person; }
        set
        {
            _person = value;
            NotifyOfPropertyChange(() => Person);
        }
    }
}

public class Person
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    [Expose("ZipCode", "zip_code")]
    public Address Address { get; set; }

    public string FullName
    {
        get { return string.Join(" ", FirstName, LastName); }
    }

    public override string ToString()
    {
        return FullName;
    }
}

public class Address
{
    public string zip_code { get; set; }
}

并绑定到您的属性:

    <TextBlock x:Name="Person_FullName" />
    <TextBlock x:Name="FirstName" />
    <TextBlock x:Name="LastName" />
    <TextBlock x:Name="ZipCode" />                  //
    <TextBlock x:Name="Person_ZipCode" />           // THESE ARE THE SAME ;)

备注:这适用于我的简单示例,但尚未经过广泛测试,因此请谨慎使用。

希望对你有帮助!:)

编辑:现在可以在GitHubNuGet上找到稍微修改过的版本

于 2012-11-12T16:03:19.793 回答