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
?
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);
}