Xamarin表单:如何创建动态的IMarkupExtension?

时间:2017-12-13 16:55:32

标签: c# xaml xamarin.forms

在我的XAML中,当我将一个元素属性绑定到我的视图模型中的一个属性时,它使用了BindingExtension类,如下所示:

public sealed class BindingExtension : IMarkupExtension<Xamarin.Forms.BindingBase>

如果我创建自己的实现IMarkupExtension的类,我只需要定义一个方法:

public Object ProvideValue (IServiceProvider serviceProvider)

但是如果我的扩展类意识到它提供的值现在已经改变了(就像BindingExtension那样),我的类如何通知XAML这个事实?

为了给出一个具体(虽然不切实际)的例子,假设我想创建一个返回时间的扩展。是的,我意识到我可以用正常的绑定做到这一点,这只是为了解释这个问题:

public class TimeExtension : IMarkupExtension
{
    public Object ProvideValue(IServiceProvider serviceProvider)
    {
        return DateTime.Now.ToString();
    }
}

如何更改此设置,以便随着时间的变化,我的扩展程序会向外界提供更新的时间值?

1 个答案:

答案 0 :(得分:1)

这应该做你想要的,但我不确定如何调用Xamarin中的UI线程,因此该部分可能需要一些工作。

(更新:请参阅下面我的解决方案OP提出的版本。它比我的更好;我没想到在扩展标记扩展上实现INPC,所以我走错了方向)。

“魔术”可以说是获取` /* TEST */ Folder parent = new Folder("Parent"); Folder sub = new Folder("Sub"); sub.setParentFolder(parent); bmeRepository.save(parent); 所以你有一个目标依赖项属性标识符和对拥有目标属性的实际依赖项对象的引用。一旦你有了这个,其余的都是微不足道的东西:制作一个计时器,更新属性而不会崩溃。最好通过绑定更新目标属性,因为那时我们不必乱用调度程序,但我猜测(错误地,或许)这种方法最大程度地减少了对Xamarin特定内容的暴露。

IProvideValueTarget

这是我在WPF中的测试XAML:

public class TimeExtension : MarkupExtension
{
    System.Threading.Timer _timer = null;
    //DispatcherTimer _timer = null;

    public TimeExtension() { }
    public TimeExtension(TimeSpan interval)
    {
        Interval = interval;
    }

    public TimeSpan Interval { get; set; } = new TimeSpan(0, 0, 0, 0, 250);

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        IProvideValueTarget provideValueTarget = 
            serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

        if (provideValueTarget.TargetObject is DependencyObject targetobj)
        if (provideValueTarget.TargetProperty is DependencyProperty targetProp)
        {
            System.Threading.TimerCallback tick 
                    = new System.Threading.TimerCallback(o =>
            {
                try
                {
                    //  This is what worked in WPF:
                    //(targetobj as Control).Dispatcher.Invoke(() =>
                    //  I *think* this may work in Xamarin. Maybe. 
                    (targetobj as NSObject).InvokeOnMainThread(() =>
                    {
                        targetobj.SetValue(targetProp, DateTime.Now);
                    });
                }
                catch (Exception ex)
                {
                    _timer.Dispose();
                    _timer = null;
                }
            });

            _timer = new System.Threading.Timer(tick, null, 0, (int)Interval.TotalMilliseconds);
            //  WPF
            /*
            _timer = new DispatcherTimer();
            _timer.Interval = Interval;
            _timer.Tick += (s, e) =>
            {
                try
                {
                    targetobj.SetValue(targetProp, DateTime.Now);
                }
                catch (Exception ex)
                {
                    targetobj.SetValue(targetProp, ex.Message);
                    _timer.Stop();
                }
            };
            _timer.Start();
            */
        }

        return DateTime.Now;
    }
}

OP的方法

使用WPF测试

<Label 
    Content="{local:Time 00:00:00.100}" 
    ContentStringFormat="yyyy-MM-dd hh:mm:ss.ffff" 
    />