WPF中具有状态的标记扩展

时间:2013-04-19 14:34:10

标签: wpf markup-extensions

我刚刚发现WPF Markup扩展实例在控件模板中重用。因此,控件模板的每个副本都会获得相同的标记扩展集。

如果您希望扩展程序为其附加的每个控件维护某些状态,则此操作无效。不知道如何解决这个问题。

1 个答案:

答案 0 :(得分:4)

不要在Markup扩展中存储状态。以另一种方式存储它。例如。

public abstract class DynamicMarkupExtension : MarkupExtension
{
    public class State
    {

        public object TargetObject { get; set; }

        public object TargetProperty { get; set; }

        public void UpdateValue(object value)
        {
            if (TargetObject != null)
            {
                if (TargetProperty is DependencyProperty)
                {
                    DependencyObject obj = TargetObject as DependencyObject;
                    DependencyProperty prop = TargetProperty as DependencyProperty;

                    Action updateAction = () => obj.SetValue(prop, value);

                    // Check whether the target object can be accessed from the
                    // current thread, and use Dispatcher.Invoke if it can't

                    if (obj.CheckAccess())
                        updateAction();
                    else
                        obj.Dispatcher.Invoke(updateAction);
                }
                else // TargetProperty is PropertyInfo
                {
                    PropertyInfo prop = TargetProperty as PropertyInfo;
                    prop.SetValue(TargetObject, value, null);
                }
            }
        }
    }

    public sealed override object ProvideValue(IServiceProvider serviceProvider)
    {
        IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
        State state = new State();
        if (target != null)
        {
            state.TargetObject = target.TargetObject;
            state.TargetProperty = target.TargetProperty;
            return ProvideValueInternal(serviceProvider, state);
        }
        else
        {
            return null;
        }

    }


    protected abstract object ProvideValueInternal(IServiceProvider serviceProvider, State state);
}

是一个基类,用于处理需要更新标记属性的问题类型 扩展名在运行时附加。例如,用于绑定到ISubject的标记扩展名为

<TextBox Text="{Markup:Subscription Path=Excenter, ErrorsPath=Errors}"/> 

使用SubscriptionExtension,如下所示。我使用它时遇到了代码问题 在模板中,但我修复了它,因此MarkupExtension本身不存储状态

using ReactiveUI.Ext;
using ReactiveUI.Subjects;
using ReactiveUI.Utils;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

namespace ReactiveUI.Markup
{
    [MarkupExtensionReturnType(typeof(BindingExpression))]
    public class SubscriptionExtension : DynamicMarkupExtension
    {
        [ConstructorArgument("path")]
        public PropertyPath Path { get; set; }

        [ConstructorArgument("errorsPath")]
        public PropertyPath ErrorsPath { get; set; }

        public SubscriptionExtension() { }

        Maybe<Exception> currentErrorState = Maybe.None<Exception>();

        public SubscriptionExtension(PropertyPath path, PropertyPath errorsPath)
        {
            Path = path;
            ErrorsPath = errorsPath;
        }

        class Proxy : ReactiveObject, IDataErrorInfo, IDisposable
        {
            string _Value;
            public string Value
            {
                get { return _Value; }
                set { this.RaiseAndSetIfChanged(value); }
            }

            public string Error
            {
                get { return currentError.Select(e => e.Message).Else(""); }
            }

            public string this[string columnName]
            {
                get { return currentError.Select(e => e.Message).Else(""); }
            }

            public IObservable<Maybe<Exception>> Errors { get; set; }

            public Maybe<Exception> currentError = Maybe.None<Exception>();

            private CompositeDisposable Subscriptions = new CompositeDisposable();
            public Proxy(IObservable<Maybe<Exception>> errors)
            {
                Errors = errors;
                var subscription = errors.Subscribe(e => currentError = e);
                Subscriptions.Add(subscription);
            }

            public void Dispose()
            {
                Subscriptions.Dispose();
            }
        }

        protected override object ProvideValueInternal(IServiceProvider serviceProvider, DynamicMarkupExtension.State state )
        {
            var pvt = serviceProvider as IProvideValueTarget;
            if (pvt == null)
            {
                return null;
            }

            var frameworkElement = pvt.TargetObject as FrameworkElement;
            if (frameworkElement == null)
            {
                return this;
            }

            DependencyPropertyChangedEventHandler myd = delegate(object sender, DependencyPropertyChangedEventArgs e){
                state.UpdateValue(MakeBinding(serviceProvider, frameworkElement));
            };

            frameworkElement.DataContextChanged += myd;

            return MakeBinding(serviceProvider, frameworkElement);
        }


        private object MakeBinding(IServiceProvider serviceProvider, FrameworkElement frameworkElement)
        {
            var dataContext = frameworkElement.DataContext;

            if (dataContext is String)
            {
                return dataContext;
            }

            ISubject<string> subject = Lens.Empty<string>().Subject;
            IObservable<Maybe<Exception>> errors = Observable.Empty<Maybe<Exception>>();
            Binding binding;
            Proxy proxy = new Proxy(errors);

            bool madeit = false;
            if (dataContext != null)
            {

                subject = GetProperty<ISubject<string>>(dataContext, Path);

                if (subject != null)
                {

                    errors = GetProperty<IObservable<Maybe<Exception>>>
                        (dataContext
                        , ErrorsPath) ?? Observable.Empty<Maybe<Exception>>();

                    proxy = new Proxy(errors);
                }
                madeit = true;
            }
            if(!madeit)
            {
                subject = new BehaviorSubject<string>("Binding Error");
            }

            // Bind the subject to the property via a helper ( in private library )
            var subscription = subject.TwoWayBindTo(proxy, x => x.Value);

            // Make sure we don't leak subscriptions
            frameworkElement.Unloaded += (e, v) => subscription.Dispose();

            binding = new Binding()
            {
                Source = proxy,
                Path = new System.Windows.PropertyPath("Value"),
                ValidatesOnDataErrors = true
            };

            return binding.ProvideValue(serviceProvider);
        }


        private static T GetProperty<T>(object context, PropertyPath propPath)
        where T : class
        {
            if (propPath==null)
            {
                return null;
            }
            try
            {
                object propValue = propPath.Path
                    .Split('.')
                    .Aggregate(context, (value, name)
                        => value.GetType()
                            .GetProperty(name)
                            .GetValue(value, null));
                return propValue as T;
            }
            catch (NullReferenceException e)
            {
                throw new MemberAccessException(propPath.Path + " is not available on " + context.GetType(),e);
            }

        }

    }
}