سؤال

أحد الأمثلة الرئيسية المستخدمة لشرح قوة الامتدادات التفاعلية (RX) هو الجمع بين أحداث الماوس الحالية في "حدث" جديد يمثل دلتا أثناء سحب الماوس:

var mouseMoves = from mm in mainCanvas.GetMouseMove()
                 let location = mm.EventArgs.GetPosition(mainCanvas)
                 select new { location.X, location.Y};

var mouseDiffs = mouseMoves
    .Skip(1)
    .Zip(mouseMoves, (l, r) => new {X1 = l.X, Y1 = l.Y, X2 = r.X, Y2 = r.Y});

var mouseDrag = from _  in mainCanvas.GetMouseLeftButtonDown()
                from md in mouseDiffs.Until(
                    mainCanvas.GetMouseLeftButtonUp())
                select md;

مصدر: مقدمة ماثيو بودويسوكي لسلسلة الإطار التفاعلي.

في MVVM ، أسعى عمومًا إلى الحفاظ على ملف .xaml.cs الخاص بي فارغًا قدر الإمكان وطريقة واحدة لتثبيت الأحداث من العرض مع الأوامر في ViewModel بحتة في الترميز تستخدم السلوك:

<Button Content="Click Me">
    <Behaviors:Events.Commands>
        <Behaviors:EventCommandCollection>
            <Behaviors:EventCommand CommandName="MouseEnterCommand" EventName="MouseEnter" />
            <Behaviors:EventCommand CommandName="MouseLeaveCommand" EventName="MouseLeave" />
            <Behaviors:EventCommand CommandName="ClickCommand" EventName="Click" />
        </Behaviors:EventCommandCollection>
    </Behaviors:Events.Commands>
</Button>

مصدر: براين جينيسيو.

يبدو أن الإطار التفاعلي أكثر توجهاً نحو نمط MVC التقليدي حيث تعرف وحدة التحكم العرض ويمكن أن تشير إلى أحداثها مباشرة.

لكنني أريد أن أحصل على كعكتي وأكلها!

كيف يمكنك الجمع بين هذين النموذجين؟

هل كانت مفيدة؟

المحلول

لقد كتبت إطار عمل يمثل استكشافاتي في هذا السؤال المسمى Reactiveui

إنه ينفذ كلاً من icommand يمكن ملاحظته ، وكذلك كائنات ViewModel التي تشير إلى تغييرات عبر iobservable ، بالإضافة إلى القدرة على "تعيين" قابلة للرسو على الممتلكات ، التي ستطلق النار بعد ذلك inotifyPropertychang كما أنه يلف الكثير من الأنماط الشائعة ، مثل وجود icommand الذي يدير مهمة في الخلفية ، ثم يعيد النتيجة إلى واجهة المستخدم.

لديّ توثيق صفر على الإطلاق في الوقت الحالي ، لكنني سأعمل على إضافة تلك المعلومات خلال الأيام المقبلة ، بالإضافة إلى تطبيق عينة قمت بترميزه

تحديث: لدي الآن الكثير من الوثائق ، تحقق من http://www.reactiveui.net

نصائح أخرى

اتضح أن الحل لمشكلتي هو إنشاء فئة تنفذ كلاً من icommand و iobservableu003CT>

يتم استخدام ICommand لربط واجهة المستخدم (باستخدام السلوكيات) ويمكن بعد ذلك استخدام iobservable ضمن نموذج العرض لإنشاء تدفقات الأحداث المركبة.

using System;
using System.Windows.Input;

namespace Jesperll
{
    class ObservableCommand<T> : Observable<T>, ICommand where T : EventArgs
    {
        bool ICommand.CanExecute(object parameter)
        {
            return true;
        }

        event EventHandler ICommand.CanExecuteChanged
        {
            add { }
            remove { }
        }

        void ICommand.Execute(object parameter)
        {
            try
            {
                OnNext((T)parameter);
            }
            catch (InvalidCastException e)
            {
                OnError(e);
            }
        }
    }
}

حيث يمكن ملاحظتهاu003CT> يظهر في تنفيذ iobservable من الصفر

عندما بدأت التفكير في كيفية "الزواج" MVVM و RX ، كان أول شيء فكرت فيه هو ObservableCommand:

public class ObservableCommand : ICommand, IObservable<object>
{
    private readonly Subject<object> _subj = new Subject<object>();

    public void Execute(object parameter)
    {
        _subj.OnNext(parameter);
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public IDisposable Subscribe(IObserver<object> observer)
    {
        return _subj.Subscribe(observer);
    }
}

ولكن بعد ذلك اعتقدت أن طريقة "MVVM القياسية" لعناصر التحكم في الربط لخصائص Icommand ليست rx'ish للغاية ، فإنها تحطم تدفق الحدث إلى توصيلات ثابتة إلى حد ما. RX أكثر عن الأحداث ، والاستماع إلى أعدم الحدث التوجيه يبدو مناسبا. هذا ما توصلت إليه:

1) لديك سلوك commandRelay الذي تقوم بتثبيته بجذر كل عنصر تحكم مستخدم يجب أن يستجيب للأوامر:

public class CommandRelay : Behavior<FrameworkElement>
{
    private ICommandSink _commandSink;

    protected override void OnAttached()
    {
        base.OnAttached();
        CommandManager.AddExecutedHandler(AssociatedObject, DoExecute);
        CommandManager.AddCanExecuteHandler(AssociatedObject, GetCanExecute);
        AssociatedObject.DataContextChanged 
          += AssociatedObject_DataContextChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        CommandManager.RemoveExecutedHandler(AssociatedObject, DoExecute);
        CommandManager.RemoveCanExecuteHandler(AssociatedObject, GetCanExecute);
        AssociatedObject.DataContextChanged 
          -= AssociatedObject_DataContextChanged;
    }

    private static void GetCanExecute(object sender, 
        CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }

    private void DoExecute(object sender, ExecutedRoutedEventArgs e)
    {
        if (_commandSink != null)
            _commandSink.Execute(e);
    }

    void AssociatedObject_DataContextChanged(
       object sender, DependencyPropertyChangedEventArgs e)

    {
        _commandSink = e.NewValue as ICommandSink;
    }
}

public interface ICommandSink
{
    void Execute(ExecutedRoutedEventArgs args);
}

2) تم توريث ViewModel الذي يخدم عنصر تحكم المستخدم من ReactiveViewModel:

    public class ReactiveViewModel : INotifyPropertyChanged, ICommandSink
    {
        internal readonly Subject<ExecutedRoutedEventArgs> Commands;

        public ReactiveViewModel()
        {
            Commands = new Subject<ExecutedRoutedEventArgs>();
        }

...
        public void Execute(ExecutedRoutedEventArgs args)
        {
            args.Handled = true;  // to leave chance to handler 
                                  // to pass the event up
            Commands.OnNext(args);
        }
    }

3) لا تربط عناصر التحكم في خصائص Icommand ، ولكن تستخدم RoutedCommand بدلاً من ذلك:

public static class MyCommands
{
    private static readonly RoutedUICommand _testCommand 
       = new RoutedUICommand();
    public static RoutedUICommand TestCommand 
      { get { return _testCommand; } }
}

وفي Xaml:

<Button x:Name="btn" Content="Test" Command="ViewModel:MyCommands.TestCommand"/>

نتيجة لذلك ، في ViewModel الخاص بك ، يمكنك الاستماع إلى الأوامر بطريقة RX للغاية:

    public MyVM() : ReactiveViewModel 
    {
        Commands
            .Where(p => p.Command == MyCommands.TestCommand)
            .Subscribe(DoTestCommand);
        Commands
            .Where(p => p.Command == MyCommands.ChangeCommand)
            .Subscribe(DoChangeCommand);
        Commands.Subscribe(a => Console.WriteLine("command logged"));
    }

الآن ، لديك قوة الأوامر الموجه (أنت حر في اختيار معالجة الأمر على أي أو حتى متعددة من علب الردود في التسلسل الهرمي) ، بالإضافة إلى أن لديك "تدفق واحد" لجميع الأوامر التي تكون أكثر جدوى على Rx من iobservable المنفصلة .

يجب أن يكون هذا قابلاً للتنفيذ تمامًا عبر التفاعل reactiveframework.

التغيير الوحيد المطلوب هو إنشاء سلوك لهذا ، ثم اتباع السلوك إلى الأمر. سيبدو شيئًا مثل:

<Button Content="Click Me">
    <Behaviors:Events.Commands>
        <Behaviors:EventCommandCollection>
            <Behaviors:ReactiveEventCommand CommandName="MouseEnterCommand" EventName="MouseEnter" />
            <Behaviors:ReactiveEventCommand CommandName="MouseLeaveCommand" EventName="MouseLeave" />
            <Behaviors:ReactiveEventCommand CommandName="ClickCommand" EventName="Click" />
        </Behaviors:EventCommandCollection>
    </Behaviors:Events.Commands>
</Button>

فقط أدرك أن EventCommand يعمل بطريقة مشابهة جدًا لكيفية عمل reactiveframework ، في هذا السيناريو. لن ترى فرقًا حقًا ، على الرغم من أن تنفيذ EventCommand سيتم تبسيطه.

EventCommand يوفر بالفعل نموذج دفع لك - عندما يحدث الحدث ، فإنه يطلق قيادتك. هذا هو سيناريو الاستخدام الرئيسي لـ RX ، لكنه يجعل التنفيذ بسيطًا.

أعتقد أن الفكرة كانت إنشاء حدث "وتر" ، في هذه الحالة ربما عملية سحب ، والتي تؤدي إلى أمر يسمى؟ سيتم ذلك إلى حد كبير بنفس الطريقة التي ستفعل بها في CodeBehind ، ولكن مع الكود في السلوك. على سبيل المثال ، قم بإنشاء جهاز DragBehavior الذي يستخدم RX لدمج أحداث Mousedown/Mousemove/Mouseup مع أمر يسمى للتعامل مع "الحدث" الجديد.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top