Search code examples
c#mvvmmvvm-lightobservablecollection

MvvmLight in a class that uses composition


I have a ViewModel, derived from MvvmLight.ViewModelBase that uses composition to reuse other classes:

Simplified version of the classes that I want to reuse in a composition:

class TimeFrameFactory
{
    public DateTime SelectedTime {get; set;}

    public ITimeFrame CreateTimeFrame() {...}
}

class GraphFactory
{
     public int GraphWidth {get; set;}

     public IGraph CreateGraph(ITimeframe timeframe) {...}
}

My ViewModel, derived from MvvmLight ViewModelBase is a composition of these two:

class MyViewModel : ViewModelBase
{
    private readonly TimeFrameFactory timeFrameFactory = new TimeFrameFactory();
    private readonly GraphFactory graphFactory = new GraphFactory();

    private Graph graph;

    // standard MVVM light method to get/set a field:
    public Graph Graph
    {
        get => this.Graph;
        private set => base.Set(nameof(Graph), ref graph, value);
    }

    // this one doesn't compile:
    public DateTime SelectedTime 
    {
        get => this.timeFrameFactory.SelectedTime;
        set => base.Set(nameof(SelectedTime), ref timeFrameFactory.SelectedTime, value);
    }

    // this one doesn't compile:
    public int GraphWidth
    {
        get => this.timeFrameFactory.GraphWidth;
        set => base.Set(nameof(GraphWidth), ref timeFrameFactory.GraphWidth, value);
    }

    public void CreateGraph()
    {
        ITimeFrame timeFrame = this.timeFrameFactory.CreateTimeFrame();
        this.Graph = this.GraphFactory.CreateGraph(timeFrame);
    }
}

Get/Set with a field works, but if I want to forward setting a property to the composite object, I can't use base.Set

set => base.Set(nameof(GraphWidth), ref timeFrameFactory.GraphWidth, value);

ref is not allowed on properties.

Of course I could write:

    public int GraphWidth
    {
        get => this.timeFrameFactory.GraphWidth;
        set
        {
            base.RaisePropertyChanging(nameof(GraphWidh));
            base.Set(nameof(GraphWidth), ref timeFrameFactory.GraphWidth, value);
            base.RaisePropertyChanged(nameof(GraphWidh));
        }
    }

Quite a nuisance if you have to do this for a lot of properties. Is there a neat way to do this, possibly similar to ObservableObject.Set?


Solution

  • Well, the base method needs to be able to both read (for comparisons) and write the passed field/property, hence the ref.

    Since you can't pass properties by reference, I think you're forced into writing another base method that either

    A) Takes getter/setter delegates. (Verbose / annoying)

    public int GraphWidth
    {
        get => this.timeFrameFactory.GraphWidth;
        set => base.Set(nameof(GraphWidth), () => timeFrameFactory.GraphWidth, x => timeFrameFactory.GraphWith = x, value);
    }
    

    or

    B) Passes an Expression<Func<T>> containing the property and uses reflection to extract the property and get/set it in the base (Slow, but potentially can extract name too)

    public int GraphWidth
    {
        get => this.timeFrameFactory.GraphWidth;
        set => base.Set(() => timeFrameFactory.GraphWidth, value);
    }