简体   繁体   中英

Trying to understand how to combine rx.net with existing MVVM project

I currently have a WPF project that makes use of MVVM. In my project, I make use of a static class which acts as a cache for collections used by the various controls in my app, something like this:

public static class AppCache
{

    public static ObservableCollection<MyObject> MyObjects { get; set; }

    private static async void GetMyObjectsAsync()
    {
        await Task.Run(() => GetMyObjects());

        Mediator.Instance.NotifyColleagues("MyObjectsUpdatedMessage", true); // A helper class that implements messaging
    }

    private static GetMyObjects()
    {

        // Get objects via service call

        ...

        MyObjects = result;
    }

    static AppCache()
    {
        MyObjects = new ObservableCollection<MyObject>();

        GetMyObjectsAsync();
    }

}

I then subscribe to the mediator in my various viewmodels:

public class MyViewModel : ViewModelBase
{

    ...

    public ObservableCollection<MyObject> MyObjects
    {
        // My ViewModelBase lets me implement INotifyPropertyChanged like this
        get { return GetValue(() => MyObjects); } 
        set { SetValue(() => MyObjects, value); }
    }

    [Message("MyObjectsUpdatedMessage")]
    private void OnMyObjectSourceUpdated(bool c)
    {
        MyObjects = AppCache.MyObjects;
    }

    public MyViewModel ()
    {
        Mediator.Instance.RegisterHandler<bool>("MyObjectsUpdatedMessage", OnMyObjectSourceUpdated);
    }

}

The problem I have with this method is that when I do things with the collections in the ViewModels (eg. add or edit a MyObject ) I then have to go back and manually update the global AppCache collection and make sure it matches up with what is in the ViewModel, then make sure that I update all of the other ViewModels using this collection to use the new value since there is no binding involved: MyOtherViewModel.MyObjects = AppCache.MyObjects

The alternative is to make GetMyObjectsAsync() public and have the AppCache update itself from the database after I make changes from my ViewModel, then use the Mediator to update all the other views using the collection. I don't like this either as it means I end up making a service call I don't want to.

What I'm trying to figure out is if there is any way to use Reactive Extensions to simplify my process, such that I can have some kind of Reactive Property defined in the AppCache which my ViewModels subscribe to, and which when updated will push its updates out to all the ViewModels, something of a mix between the two options available to me (Manually update the AppShared collection but then have all its subs notified without needing the mediator).

I suppose really what I want is to have a property that is essentially bindable and shareable between ViewModels.

Is there any kind of Reactive Property I can use to achieve this sort of thing? Something like:

EDIT:

I was able to get the subscription to work as follows, but this is again similar to the option of using the Mediator as I have to call NotifyMyObjectChanged whenever I update MyObjects. Is there a way to make MyObjectsObservable 'listen' for changes to MyObjects and automatically call NotifyMyObjectChanged ? If there isn't a way to do this, is there a benefit to using RX for this over the Mediator?

public static class AppCache
{

    public static ObservableCollection<MyObject> MyObjects { get; set; }

    public static IObservable<ObservableCollection<MyObject>> MyObjectsObservable => _mySubject; // C# 6 syntax

    public static Subject<ObservableCollection<MyObject>> _mySubject { get; set; }

    private static async void GetMyObjectsAsync()
    {
        await Task.Run(() => GetMyObjects());

        NotifyMyObjectChanged() // this basically just replaces the mediator
    }

    private static GetMyObjects()
    {                   
        ...         
        MyObjects = result;
    }

    private static void NotifyMyObjectChanged()
    {
        _mySubject.OnNext(MyObjects);
    }   

    static AppCache()
    {
        _mySubject = new Subject<ObservableCollection<MyObject>>();

        GetMyObjectsAsync();
    }    

}

public class MyViewModel : ViewModelBase
{   

    ObservableCollection<MyObject> MyObjects 
    {
        get { return GetValue(() => MyObjects); }
        set { SetValue(() => MyObjects, value); }
    }


    IDisposable _subscription { get; }

    MyViewModel()
    {
        _subscription = AppCache.MyObjectsObservable.Subscribe (HandleMyObjectChanged);
    }

    private void HandleMyObjectChanged(ObservableCollection<MyObject> myObjects)
    {
        MyObjects = myObjects;
    }

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

So what you want to do is something like this:

public static class AppCache
{
    static AppCache()
    {
         _mySubject = new Subject<MyObject>();
    }

    private static void NotifyMyObjectChanged(MyObject object)
    {
        _mySubject.OnNext(object);
    }

    public static IObservable<MyObject> MyObjectsObservable 
    { 
        get { return _mySubject; }
    }
}

public class MyViewModel : ViewModelBase
{            
     MyViewModel()
     {
          _subscription = AppCache.MyObjectsObservable.
              .Where(x => x == value)
              .Subscribe (HandleMyObjectChanged);
     }

     private void HandleMyObjectChanged(MyObject object)
     {
        ... do work here ....
     }

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

In this case what you're basically doing here is sending a notification to your view model that something on your MyObject has changed. You can take the object variable from the handler and use that to copy the changed properties into your view model. Alternatively you could send a "message" class that has a list of properties and their new values, that might be a little lighter than sending the entire object.

The other thing to keep in mind is that the Observable might send the "event" on a thread other than the UI thread. There's a Subscribe override that lets you specific a TaskScheduler to use, so you can specify the UI scheduler so you don't have to do the marshalling yourself.

As I said in my comment, there are tons of ways to do this, so you'll have to play around to find something that fits your needs the best, but hopefully this gives you some direction.

UPDATE

So here's a bit of an update to your AppCache code (from your edit), but your question is basically that you want to get rid of the mediator and replace it with RX. In the end, you still end up with a mediator of some sort, you just have to decide whether RX gives you something more than your own implementation of a mediate would. I would (and do) use RX, but it's really a preference thing, so you're going to have to do some research on your own.

This shows you the use of the Observable.FromEventPattern function, which is pretty useful and gives you a nice shortcut:

public static class AppCache
{

    ObservableCollection<MyObject> MyObjects { get; set; }

    public static IObservable<ObservableCollection<MyObject>> MyObjectsObservable { get; private set; }

    private static async void GetMyObjectsAsync()
    {
        await Task.Run(() => GetMyObjects());

        NotifyMyObjectChanged() // this basically just replaces the mediator
    }

    private static GetMyObjects()
    {                   
        ...         
        MyObjects = result;
        MyObjectsObservable = Observable.FromEventPattern(h=>MyObjects.CollectionChanged += h, h=>MyObjects.CollectionChanged -= h);
    }

    static AppCache()
    {
        GetMyObjectsAsync();
    }    

}

I wanted to show you the usuage but this code isn't perfect, if a subscriber calls Subscribe before the MyObjectsObservable is created, obviously it would blow up.

Here's another link that shows you a lot of the stuff that RX can do, that to me makes it a great tool: http://www.introtorx.com/uat/content/v1.0.10621.0/04_CreatingObservableSequences.html#

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM