So I wrote this, and I'm pretty happy with it, and I thought I'd share. I hate magic strings, and I'm a big fan of static typing, and I usually build my interfaces in code rather than XAML. But at the same time, I don't like specifying types where they can be inferred. So here are a couple helper classes I made to help out:
public struct ViewModelBinder<TViewModel> {
readonly BindableObject target;
public ViewModelBinder(BindableObject target)
{
this.target = target;
}
public ViewModelBinder<TViewModel> Bind(Expression<Func<TViewModel, object>> sourceProperty,
BindingMode mode = 0, IValueConverter converter = null, string stringFormat = null)
{
// Add more default target properties here as needed
var targetProperty = target is Entry ? Entry.TextProperty
: target is DatePicker ? DatePicker.DateProperty
: target is Image ? Image.SourceProperty
: target is TextCell ? TextCell.TextProperty : null;
return Bind(targetProperty, sourceProperty, mode, converter, stringFormat);
}
public ViewModelBinder<TViewModel> Bind(BindableProperty targetProperty, Expression<Func<TViewModel, object>> sourceProperty,
BindingMode mode = 0, IValueConverter converter = null, string stringFormat = null)
{
target.SetBinding<TViewModel>(targetProperty, sourceProperty, mode, converter, stringFormat);
return this;
}
public BindableObject Target { get { return target; } }
}
public abstract class BoundContentPage<TViewModel> : ContentPage where TViewModel : class {
protected BoundContentPage(TViewModel viewModel)
{
BindingContext = viewModel;
}
protected void Bind(BindableProperty targetProperty, Expression<Func<TViewModel, object>> sourceProperty,
BindingMode mode = 0, IValueConverter converter = null, string stringFormat = null)
{
this.SetBinding<TViewModel>(targetProperty, sourceProperty, mode, converter, stringFormat);
}
protected void Bind(BindableObject target, BindableProperty targetProperty, Expression<Func<TViewModel, object>> sourceProperty,
BindingMode mode = 0, IValueConverter converter = null, string stringFormat = null)
{
target.SetBinding<TViewModel>(targetProperty, sourceProperty, mode, converter, stringFormat);
}
public T Control<T>(T obj, Action<ViewModelBinder<TViewModel>> init) where T : BindableObject
{
init(new ViewModelBinder<TViewModel>(obj));
return obj;
}
public TViewModel ViewModel {
get { return BindingContext as TViewModel; }
set { BindingContext = value; }
}
}
Using BoundContentPage
instead of ContentPage
, along with Control()
, allows me to create things concisely:
Content = new TableView {
Intent = TableIntent.Form,
Root = new TableRoot {
new TableSection("Section") {
Control(new TextCell(), b => b.Bind(vm => vm.SomeTextProperty)),
},
new TableSection("NextSection") {
Control(new TextCell(), b => b.Bind(vm => vm.AnotherTextProperty)),
},
},
};
Note that I use struct
for my ViewModelBinder
helper so it gets allocated on the stack and has no GC implications. In cases like DataTemplate
I just instantiate the VMB directly since it's hard to avoid typing the name of the type of the underlying object.
ItemTemplate = new DataTemplate(() => {
return new ViewModelBinder<StaffListPageItem>(new ImageCell())
.Bind(vm => vm.FullName)
.Bind(ImageCell.ImageSourceProperty, vm => vm.ImageUri)
.Target;
}),
Thoughts?