layout | title | category | order |
---|---|---|---|
documentation |
Data binding |
Fundamentals |
1 |
Data binding is the key technology that MVVM relies on, to link Views with their View-Models.
Data binding provides and maintains the automated Two-Way connection between View and ViewModel. A good understanding of data binding is essential for every MVVM developer.
Within MvvmCross, data binding was initially built to mirror the structure provided by Microsoft in their XAML based frameworks, but in more recent developments MvvmCross has extended data binding in new directions.
This article focuses first on the core 'Windows' data binding approach, but then later extends to some of the newer ideas.
In this structure, for each binding:
- C# properties are used in both View and ViewModel
- A single View property is 'bound' - connected - to a ViewModel property
- is specified with a Mode which gives a direction for data flow (One-Way, Two-Way, etc.)
- can optionally be specified with a ValueConverter - and this can optionally also be parameterised
- can also optionally be specified with a FallbackValue for when binding fails.
C# properties are used for data binding on both the View and the ViewModel.
On the ViewModel, these properties often look like:
private string _myProperty;
public string MyProperty
{
get => _myProperty;
set
{
_myProperty = value;
RaisePropertyChanged(() => MyProperty);
// take any additional actions here which are required when MyProperty is updated
}
}
Note: MvvmCross provides helper methods to assign the backing field and fire the
PropertyChanged
event, after checking whether value actually changed. Consider usingSetProperty()
for this, which is present onMvxViewModel
andMvxPropertyChanged
.
This pattern uses a local private backing variable to store the current value, and relies on RaisePropertyChanged
to signal changes in the value to any listening Views.
In the View:
-
on Windows platforms,
DependencyProperty
objects are used to store variable values. TheseDependencyProperty
objects provide well-known mechanisms:- to allow both
get
andset
of the value within the View. - to listen for changes on the value within the View - e.g. when the user enters new text into a
TextBox
- to allow both
-
in new C# platforms like Xamarin.Android and Xamarin.iOS:
- most commonly
- normal C# properties are used to get and set variable values - typically these C# properties are wrapper properties that Xamarin has created around native Java or Objective-C methods
- normal C# events are used to determine when these variable values have changed - e.g. when a user has entered a value.
- sometimes - when there isn't a neat property and event based mapping available
- custom C# methods have to be used to get and set the variable values
- custom Java listeners or Objective-C delegates have to be used to detect when the UI View state changes (e.g. when the user enters text or taps on a button).
- most commonly
For more info on the details on implementing custom bindings, see the official documentation
Using the View and ViewModel properties described above, it is common for a ViewModel C# property to be used to model the value of a View property.
For example:
- if your View contained a
CheckBox
which has anIsChecked
property - then your ViewModel might contain a property:
private bool _rememberMe;
public bool RememberMe
{
get => _rememberMe;
set => SetProperty(ref _rememberMe, value);
}
- then a binding might connect together
IsChecked
on the View withRememberMe
in the ViewModel.
Data binding also enables a ViewModel to react to 'events' which occur in a View - e.g. for a ViewModel to respond to events such as a button being pressed.
The technique generally used for this is for the ViewModel to expose special Command
properties which can be bound to corresponding Command
properties on the View.
For example, a CheckBox
might have a CheckedCommand
and this might be bindable to a RememberMeChangedCommand
on the ViewModel.
Within Windows, for sometimes, when a View has not exposed
There are 4 modes in which properties in the View can be bound to properties in the ViewModel:
- One-Way
- One-Way-To-Source
- Two-Way
- One-Time
One-Way
- This binding mode transfers values from the ViewModel to the View
- Whenever the property changes within the ViewModel, then the corresponding View property is automatically adjusted.
- This binding mode is useful when when showing, for example, data which is arriving from a dynamic source - like from a sensor or from a network data feed.
- In Windows/Xaml, this is very often the default binding mode - so it is the mode used when no other is selected.
One-Way-To-Source
- This binding mode transfers values from the View to the ViewModel
- When the View property changes then the corresponding ViewModel will be updated.
- This binding mode is useful when collecting new data from a user - e.g. when a user fills in a blank form.
- In practice, this binding mode is rarely used - most developers choose to use Two-Way instead.
Two-Way
- This binding mode transfers values in both directions
- Changes in both View and ViewModel properties are monitored - if either changes, then the other will be updated.
- This binding mode is useful when editing entries an existing form, and is very commonly used by developers.
- Where MvvmCross had created new bindings, then this is very often the default binding mode MvvmCross tries to use.
One-Time
- This binding mode transfers values from ViewModel to View
- This transfer doesn't actively monitor change messages/events from the ViewModel
- instead this binding mode tries to transfer data from ViewModel to View only when the binding source is set. After this, the binding doesn't monitor changes and doesn't perform any updates, unless the binding source itself is reset.
- This mode is not very commonly used, but can be useful for fields which are configurable but which don't tend to change after they have initially been set.
- In MvvmCross, we use One-Time binding when setting static text from language files - this is because it's common for the user to select a language, but once chosen, it's uncommon for the user to then change that language.
A ValueConverter
is a class which implements the IValueConverter
interface.
This interface provides two object
level conversion methods:
Convert
- providing a simple mechanism for changing values fromViewModel
toView
ConvertBack
- providing a simple mechanism for changing values back fromView
toViewModel
It is very common for a ValueConverter
to only be used for converting values for display in the View
. In this case, only the Convert
method is implemented.
Because the IValueConverter
interface is not exactly the same across all platforms, MvvmCross instead provides IMvxValueConverter
interface which can be mapped to IValueConverter
on each platform.
Further, MvvmCross provides some supporting base classes to help with writing value converters:
MvxValueConverter
MvxValueConverter<TFrom>
MvxValueConverter<TFrom, TTo>
As an example, a LengthValueConverter
which is only used to help display the length of a string - with no ConvertBack
use - might be implemented:
public class LengthValueConverter
: MvxValueConverter<string, int>
{
protected override int Converter(string value, Type targetType, object parameter, CultureInfo cultureInfo)
{
if (value == null)
return 0;
return value.Length;
}
}
ValueConverters can also be provided with a parameter
- this can sometimes be useful to reuse a single value converter in different situations. For example, a TrimValueConverter
might be able to take the characters to trim in its parameter
.
Sometimes the ViewModel source property for the ViewModel isn't available.
For example:
- suppose you bind a label's
Text
toCustomer.FirstName
on the ViewModel - and that you specify a
FallbackValue='no customer'
- if
Customer
isnull
at any point, thenCustomer.FirstName
will be treated asUnsetValue
- and so the fallback 'no customer' will be displayed.
Notes:
- in this example, if
Customer
is an object which has anull
FirstName
then this will not cause the Fallback to be used. The Fallback is there only for cases where the value isUnset
- where the binding engine cannot find a value. Anull
value is still a value - so it doesn't trigger the Fallback. - one situation which can trigger an
UnsetValue
is if anException
is thrown - e.g. during the evaluation of a ValueConverter. In this case, the binding engine will treat thisException
as anUnsetValue
and so theFallbackValue
will be used.
While we have used the terms View
and ViewModel
throughout this article, you will also see DataContext
used in this article and in code.
DataContext
is simply the C# name for the property on a View
to which the ViewModel
is assigned.
ViewModel
and DataContext
can be considered as the same thing.
Data binding has always been at the heart of MvvmCross, and the functionality has grown over the different versions of MvvmCross.
The evolution of MvvmCross data binding can be trace over a path of:
- JSON
- Swiss
- Fluent
- Tibet, Rio and the future!
JSON
In the first versions of MvvmCross the data binding framework used strings based on JSON to achieve data binding.
This JSON version is now retired - it is no longer easily available in MvvmCross v3. If you come across any samples or documents which use JSON binding, then be aware that these samples are now out of date.
Swiss
The replacement for JSON data binding was called 'Swiss'. It provides the same functionality as the JSON binding, but used a cleaner, less verbose syntax that is easier to use.
For example:
- a binding in JSON which was written:
{
'Text':
{
'Path':'TweetText',
'Converter':'RemainingLength',
'ConverterParameter':140
}
}
- could be rewritten in Swiss using:
Text TweetText,
Converter=RemainingLength,
ConverterParameter=140
Fluent
In addition to providing text-format binding statement which can be easily included in xml layout files, MvvmCross also provides a C# based syntax to enable bindings to be easily constructed using code.
This binding syntax is referred to as 'Fluent' bindings.
As an example:
- the text binding:
Text TweetText,
Converter=RemainingLength,
ConverterParameter=140
- might be rewritten using fluent bindings as:
this.CreatingBinding(label)
.For(l => l.Text)
.To(vm => vm.TweetText)
.WithConversion("RemainingLength", 140);
Fluent binding is especially useful in the iOS and OSX platforms where the Xml layout formats are not easily human-editable.
Tibet, Rio and the future
As the MvvmCross binding format evolved from its JSON origins through to Swiss and Fluent, the improvements were largely cosmetic. These cosmetic changes offered improved developer productivity through easier-to-use binding syntax, and did so without offering any changes in functionality in the underlying data-binding itself.
The future of MvvmCross continues to aim to include cosmetic improvements, but also aims to deliver functional enhancements as well.
The current ideas for improvements and enhancements include:
- Tibet binding
- multi-binding
- ValueCombiners
- literal-binding
- binding macros
- functional syntax for ValueConverters and ValueCombiners
- nested value conversion
- Rio binding
- Auto-Command binding
Using these ideas, then a binding like:
Text TweetText,
Converter=RemainingLength,
ConverterParameter=140
might be rewritten:
Text RemainingLength(TweetText,140)
or perhaps even:
Text 140 - Length(TweetText)
These ideas and their current development status are discussed further later in this article.
Beyond these, of course, the opportunity is there for plenty more ideas and improvements from the community - the evolution of MvvmCross and its data binding is driven by real users and the invention and ideas their real apps require.
As discussed above, JSON binding is not supported within MvvmCross v3 or later.
Swiss binding syntax allows a basic binding from a View
$Target$ $SourcePath$
where $Target$
must always be the direct path to the View property - e.g.:
- Text
- IsChecked
- Value
- ...
$SourcePath$
can be any C# style path to the property on the ViewModel or on a sub-object exposed using a property - e.g.:
- UserId
- RememberMe
- Password
- Customer.FirstName
- Customer.Address.City
- Customer.Orders[0].Date
- Customer.Orders[0].Total
- Customer.Cards["Primary"].Expiry
- Customer.Cards["Primary"].Number
- ...
Beyond this basic $TargetPath$
to $SourcePath$
syntax:
-
If
$SourcePath$
is omitted or a single period "." is used, then the Source used is the whole of the ViewModel. -
If a converter is needed, then this can be added using:
, Converter=$ConverterName$
where $ConverterName$
is the name of the Value Converter to use - which typically is the class name without it's ValueConverter
postfix - e.g. the name "Length" would be used for the class LengthValueConverter
- If a ConverterParameter is needed then this can be added using:
, ConverterParameter=$ParameterValue$
where $ParameterValue$
is one of:
-
a quotation or double-quotation delimited
string
-
the word 'null' meaning C#
null
. -
the word 'true' or 'false' providing a
bool
value -
an integer number - which will be parsed as a
long
-
a floating point number - which will be parsed as a
double
-
If a FallbackValue is needed then this can be added using:
, FallbackValue=$FallbackValue$
where $FallbackValue$
has the same permitted contents as $ParameterValue$
above, but can also additionally be:
- the
ToString()
representation of an Enum value.
This is especially useful in, for example, the binding of Visibility
properties.
- If a specific Binding Mode is needed, then this can be added:
, Mode=$WhichMode$
where $WhichMode$
is one of:
-
OneWay
-
OneWayToSource
-
TwoWay
-
OneTime
-
Default
-
Where multiple bindings are needed, these can be separated using a semi-colon
-
One very specific extension to 'Swiss' binding is the
CommandParameter
binding, this binding uses an implicit ValueConverter to specify the parameter for anICommand
invocation. This is specified using:
, CommandParameter=$CPValue$
where $CPValue$
is a literal value similar to $ParameterValue$
above
Some examples of Swiss binding statements are:
Text Customer.FirstName
Bind the Text
property to Customer.FirstName
on the ViewModel.
Text Title, Converter=Length
Bind the Text
property to Title
on the ViewModel, but apply the Length
value converter - which will normally be a default instance of the class LengthValueConverter
.
Text Order.Amount, Converter=Trim, ConverterParameter='£'
Bind the Text
property to Order.Amount
on the ViewModel, but apply the Trim
value converter, passing it the string "£".
Text Order.Amount, Converter=Trim, ConverterParameter='£', FallbackValue='N/A'
Bind the Text
property to Order.Amount
on the ViewModel, but apply the Trim
value converter, passing it the string "£". If no Order
is available, or if the Order object doesn't have an Amount
value, then display "N/A"
Value Count, Mode=TwoWay
Bind the Value
property to Count
on the ViewModel, and ensure this binding is both from View to ViewModel and from ViewModel to View.
Click DayCommand, CommandParameter='Thursday'
Bind the Click
event to the DayCommand
property on the ViewModel (which should implement ICommand
). When invoked, ensure that Execute is passeda parameter value of "Thursday"
The fluent syntax provides a C# way to create bindings.
This syntax is generally done using the CreateBindingSet<TView, TViewModel>
helper.
The syntax includes:
Bind($ViewObject$)
where $ViewObject$
is the view target for binding.
For(v => v.$ViewProperty$)
where $ViewProperty$
is the property on the view for binding.
If
For
is not provided, then the default view property is used. [Look at the tables at the bottom of this page][#default-view-properties] to see all the default bindings used for each platforms control type.
To(vm => vm.$ViewModelPath$)
where $ViewModelPath$
is the path to the view model 'source' property for binding.
OneWay()
TwoWay()
OneWayToSource()
OneTime()
all of which provide the mode for the binding
WithConversion($name$, $parameter$)
where $name$
is the name of the value converter to use, and $parameter$
is the parameter to pass in.
Using this syntax, an example binding set is:
var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(nameLabel)
.For(v => v.Text)
.To(vm => vm.Customer.FirstName);
set.Bind(creditLabel)
.For(v => v.Text)
.To(vm => vm.Customer.Total)
.WithConversion("CurrencyFormat", "$");
set.Bind(cardLabel)
.For(v => v.Text)
.To(vm => vm.Customer.Cards["Primary"].Number)
.WithConversion("LastFour")
.OneWay()
.FallbackValue("N/A");
set.Bind(warningView)
.For(v => v.Hidden)
.To(vm => vm.Customer.Alert)
.WithConversion("Not")
.FallbackValue(true);
set.Apply();
Note: when using a fluent binding, always remember to use .Apply()
- if this is missed then the binding won't ever be created.
In addition to the Expression
based Fluent bindings, String
and Extension Method
based Fluent bindings are also available. They are particularly useful for situations where bindings are needed to View events or to binding targets which are not fully exposed as C# properties. For example, even though a UIButton
does not have a Title
property in C#, a Title
property can still be set via the use of custom bindings:
set.Bind(okButton)
.For("Title")
.To(vm => vm.Caption);
set.Bind(okButton)
.For(c => c.BindText())
.To(vm => vm.Caption);
Tibet binding includes several ideas which extend Swiss binding.
Tibet has been carefully designed so that it is backwards compatible - any existing Swiss bindings should work within Tibet.
The core parts of Tibet are:
- multi-binding
- value combiners
- literal-binding
- binding macros
- functional syntax for ValueConverters and ValueCombiners
- nested value conversion
Multi-binding
In Swiss binding, each binding can only reference a single ViewModel property path.
This meant that if a ViewModel had 2 properties like FirstName
and LastName
, then the main way to create a display of the ful name was to create a new ViewModel property - e.g.:
private string _firstName;
public string FirstName
{
get => _firstName;
set
{
if (SetProperty(ref _firstName, value))
RaisePropertyChanged(() => FullName);
}
}
private string _lastName;
public string LastName
{
get => _lastName;
set
{
if (SetProperty(ref _lastName, value))
RaisePropertyChanged(() => FullName);
}
}
public string FullName => _firstName + " " + _lastName;
With Multi-Binding, the addition of the FullName
property is no longer necessary - instead the binding can be written inside the binding expression directly as:
Text FirstName + ' ' + LastName
This multi-binding will cause Text
to automatically be updated whenever either of FirstName
or LastName
change.
ValueCombiners
A ValueCombiner is a new technique provided by Tibet binding in order to merge multiple sources into a single value.
For example, the multi-binding example above used two Add
value combiners in order to link together three inputs:
FirstName
- ' '
LastName
There are a small number of ValueCombiners provided within Tibet, including:
If(test, if_true, if_false)
- takes 3 inputs:- a boolean test value
- an if_true value which is used if the test value is true
- an if_false value which is used otherwise
Format(format, args...)
- takes 1 or more inputs- a string value which evaluates to a format string template
- 0 or more args which will be evaluated in the string template
Add(one,two)
- takes 2 arguments which it 'combines' together. For two strings, this means concatenation. For two doubles or two longs it means numeric addition. For mixtures of those items, the result is currently not fully specified.GreaterThan(one, two)
- takes 2 arguments and attempts to apply greater than logic to them. As withAdd
this logic is straight-forward for two strings, two doubles or two longs, but is not well-defined for other object combinations.
Notes:
- some ValueCombiners are also available in
operator
form - e.g.Add
can be used as+
andGreaterThan
can be used as>
- combination is an interpretation, not a compilation step - especially because dynamic code generation is not supported on all MvvmCross platforms.
- for direct comparison/combination of simple
string
,long
anddouble
values, this 'interpretation' should work well. Using combiners for more complicated combinations is less well defined. - currently ValueCombiners typically try to use
long
rather thanint
anddouble
rather thanfloat
.
Important note: The current interface of value combiners is currently a proposal and working prototype only. As we learn more about the real use, benefits and challenges of value combination we may revise the API, including making breaking changes to any value combiners produced by the community. In particular, it's possible we may make changes to the type safety of the APIs, and we may try to reduce the complexity of the APIs - as Value Combiners are currently quite 'open' in the source/target types they accept and this makes developing them quite complicated.
Literal binding
As we've seen in the previous Multi-Binding and Value-Combining steps, literals can now be included in Tibet binding expressions.
For example, a binding:
Value 100 * Ratio
uses the literal 100 to provide a way of translating a Ratio
into a Percentage
.
Or a binding:
Value 'True'
uses a literal string which is automatically converted to a boolean if Value is a boolean property.
Or a binding:
Value Format('Hello {1} - today is {0:ddd MMM yyyy}', TheDate, Name)
uses a literal string to assist formating TheDate
and Name
Binding macros
Binding macros are not yet implemented.
Ideas being considered in this area include:
- access to
parent
,global
andnamed(name)
binding contexts - access to shared variables - e.g, shared numbers, strings and Colors which could provide more theming/styling options
- access to i18n resources to make text localization more straight-forward
It is likely that that prefix characters, such as $
, #
or @
, might be used as simple markers to enable the identification of these 'macros'
Functional syntax for ValueConverters and ValueCombiners
This change allows shorter simpler binding expressions to be used.
This means that a Swiss binding of:
Text TweetText,
Converter=RemainingLength,
ConverterParameter=140
can be rewritten in Tibet as:
Text RemainingLength(TweetText, 140)
Note that the function name space is shared between the Value Combiners and the Value Converters. When looking for a name, MvvmCross first looks for a matching ValueCombiner, and then second for a matching ValueConverter.
Nested value conversion
In addition to supporting multiple bound source values, Tibet binding further introduces nested evaluation of value converters and combiners.
For example, Length and Trim value converters could be applied with the Add value combiner as:
Text Length(Trim(FirstName + ' ' + LastName))
A Mild Warning
Tibet binding provides developers with many options for more advanced bindings.
This advancement is, of course, not free - it does come with a small memory and processing cost during both the construction and the execution of the bindings.
In general, this additional overhead is very small and so should not be of concern to developers. However, it's always important to be aware of your application's performance - so always consider how a binding will be constructed and evaluated, especially when applying large numbers of bindings, when applying bindings within loops (collections) or when applying bindings to data which changes very frequently. Always consider applying source (ViewModel-based) data manipulation, writing a single optimized combiner/converter or consider simple OneTime
binding as potential ways to avoid performance issues.
Within ViewModels, Mvvm in C# has always been centred around the INotifyPropertyChanged
interface.
This interface is typically implemented around C# properties which look like:
private string _lastName;
public string LastName
{
get => _lastName;
set => SetProperty(ref _lastName, value);
}
and this is further enhanced using ICommand
properties for action callbacks - e.g.
private ICommand _submitCommand;
public ICommand SubmitCommand
{
get
{
_submitCommand = _submitCommand ?? new MvxCommand(DoSubmit);
return _submitCommand;
}
}
This syntax is well understood by experienced Mvvm developers, but can also appear quite verbose when dealing with very small view models.
Some developers have worked around this verbosity by using techniques such as post-compilation injection of code - e.g. using the AOP Property plugin from Fody.
Rio binding offers developers a different approach - using the new FieldBinding and MethodBinding plugins.
FieldBinding
With the field binding plugin, MvvmCross data binding can use ViewModel public fields as data-sources for binding - e.g.
public string LastName;
Further, to provide events to drive UI updates, a lightweight INotifyChanged
interface has been added, along with abbreviated helper interfaces and classes - INC<T>
and NC<T>
. These can be used as:
public readonly INC<string> LastName = new NC<string>();
A LastName
declared in this way can be databound exactly as te earlier INotifyPropertyChanged
-based property:
Text LastName
Further, the underlying string
field can be accessed in code using:
LastName.Value = "Hello";
Mvx.Trace("Current value is {0}", LastName.Value);
To use FieldBinding, import the Field binding plugin into both your core
and your UI projects.
Notes:
- In addition to the syntactic changes of Rio, there are also some slight performance improvements - achieved by avoiding some Reflection-based get/set and by avoiding string-based event notifications.
INotifyChanged
binding has no way to support theINotifyPropertyChanged
feature 'all changed' which is achieved by signalling a property change with a null or empty property name.INotifyChanged
itself is a very simple interface - so you can easily implement your own classes to implement this if you require extensions.
MethodBinding
With the method binding plugin, MvvmCross data binding can use ViewModel public methods as sources for ICommand
without declaring an ICommand
property.
For example, a method
public void GoHome()
{
ShowViewModel<HomeViewModel>();
}
can be used in binding as:
Click GoHome
Where a single argument is available within the source method, Method Binding uses the command parameter for this call. This is useful in, for example, list item selection events - e.g.:
public void ShowDetail(ListItem item)
{
ShowViewModel<DetailViewModel>(new { id = item.Id} );
}
bound with:
ItemClick ShowDetail
To use MethodBinding, import the Method binding plugin into just your UI projects - there is no 'core' component required for these plugins.
Note: One important feature sometimes used in Windows Xaml binding but poorly supported by MvvmCross is the CanExecute
/CanExecuteChanged
functionality on ICommand
. In Xaml binding this property and event pair can be used to enable/disable UI controls such as buttons.
However, in MvvmCross, this auto-enable/disable binding isn't currently widely supported - with support instead being given to secondary binding properties - e.g. to pairs of bindings like:
Click GoHome; IsEnabled CanGoHome
The Rio Effect
A view model built using Rio will not be to every developer's liking.
However, the effect on the code-size and readability can be striking.
A Rio INotifyChanged
ViewModel like:
public class MathsViewModel
{
public readonly INC<double> SubTotal = new NC<double>();
public readonly INC<double> Percent = new NC<double>();
public void Calculate()
{
Total.Value = SubTotal.Value * Percent.Value;
}
public readonly INC<double> Total = new NC<double>();
}
is equivalent to a INotifyPropertyChanged
ViewModel of:
public class MathsViewModel
{
private double _subTotal;
public double SubTotal
{
get => _subTotal;
set => SetProperty(ref _subTotal, value);
}
private double _percent;
public double Percent
{
get => _percent;
set => SetProperty(ref _percent, value);
}
private ICommand _calculateCommand;
public ICommand CalculateCommand;
{
get
{
_calculateCommand = _calculateCommand ?? new MvxCommand(Calculate);
return _calculateCommand;
}
}
private void Calculate()
{
Total = SubTotal * Percent;
}
private double _total;
public double Total
{
get => _total;
set => SetProperty(ref _total, value);
}
}
BindingEx - Tibet and Rio in Xaml
Xaml is a platform and product from Microsoft which offers excellent tooling, lots of extensibility for adding new controls, but only limited extensibility for adding customization.
Unfortunately, this means MvvmCross can't intercept the 'normal' Xaml binding syntax which might look like:
Text="{Binding FirstName}"
However, MvvmCross Swiss, Tibet and Rio binding can be enabled through AttachedProperties
In particular two AttachedProperties
is supplied in the BindingEx package:
mvx:Bi.nd
- for bindingsmvx:La.ng
- for internationalization extensions
To add these properties to your Windows Uwp or WPF MvvmCross app:
- include the MvvmCross.Binding package
- include an additional step in Setup which initializes the WindowsBinding framework
protected override void InitializeLastChance()
{
base.InitializeLastChance();
var builder = new MvxWindowsBindingBuilder();
builder.DoRegistration();
}
-
in your Xaml files include an xml attribute for
mvx
- this will be different according to the platform: -
Uwp
xmlns:mvx="using:mvx"
-
WPF
xmlns:mvx="clr-namespace:MvvmCross.Platforms.Wpf.Binding;assembly=MvvmCross.Platforms.Wpf"
-
in your Xaml files you can now include bindings within tags such as:
<TextBlock mvx:Bi.nd="Text Customer.FirstName; Visible=ShowFirstName" />
-
for design-time support, you may also need to pull in additional value converters into the Xaml namespace. For more on this, see slodge's tutorial
Once installed, the syntax within these AttachedProperties
bindings is exactly the same as within all other Swiss and Tibet binding - and this binding functionality can be extended with custom bindings, with FieldBinding, etc - just as in MvvmCross on non-Xaml platforms.
The framework that enables the Rio and Tibet binding extensions is interface-based and is built upon the small CrossCore
platform which underpins MvvmCross
.
We're excited by the possibilities that this framework can provide - by the inventions that the community can now develop.
Anyone wishing to experiment with creating their own source binding plugins is encouraged to get started by looking at the source code for the MethodBinding and FieldBinding plugins.
This change will add a generic "WithConversion" method. This will allow developers to strongly type the use of value converters, making refactoring a lot easier and more save. For example:
set.Bind(textField).To(vm => vm.Counter).WithConversion<SomeValueConverter>();
Add something about the Generic implementation of IMvxTargetBinding #1610
Dictionary conversion allows for mapped conversions to be set up within the view's binding code, useful for quickly binding things like icons or colors to state properties.
set.Bind(button).To(vm => vm.readonly)
.WithDictionaryConversion(new Dictionary<bool, Icon>
{
{true, GreyIcon},
{false, BlueIcon}
});
Additionally, a fallback can be supplied for when a key cannot be found against the supplied Dictionary.
set.Bind(button).To(vm => vm.readonly)
.WithDictionaryConversion(new Dictionary<bool, Icon>
{
{true, GreyIcon},
{false, BlueIcon}
}, RedIcon);
Note : This feature is only available in fluent binding.
If you want to dynamically remove individual bindings after you have applied them to your view you need to add a ClearBindingKey
to your binding descriptions. The ClearBindingKey
can be any object type.
Individual binding
bindingSet.Bind(_inputText)
.For(v => v.Text)
.To(vm => vm.TextValue)
.WithClearBindingKey(nameof(_inputText));
Binding set (applied to all descriptions in the set)
bindingSet.Bind(_inputText)
.For(v => v.Text)
.To(vm => vm.TextValue);
bindingSet.ApplyWithClearBindingKey(nameof(FluentBindingView));
To remove the binding using the ClearBindingKey
you can make use of ClearBindings
extension on the IMvxBindingContextOwner
this.ClearBindings(nameof(FluentBindingView));
The tables in this section describe the default view properties used in a Fluent binding when the For
method chain is not provided.
Android
Base Control | Default |
---|---|
Android.Widget.Button | Click |
Android.Widget.CheckBox | Checked |
Android.Widget.TextView | Text |
Android.Widget.CompoundButton | Checked |
Android.Widget.SeekBar | Progress |
Android.Widget.SearchView | Query |
MvvmCross.Binding.Droid.Views.MvxListView | ItemsSource |
MvvmCross.Binding.Droid.Views.MvxLinearLayout | ItemsSource |
MvvmCross.Binding.Droid.Views.MvxGridView | ItemsSource |
MvvmCross.Binding.Droid.Views.MvxRelativeLayout | ItemsSource |
MvvmCross.Binding.Droid.Views.MvxFrameLayout | ItemsSource |
MvvmCross.Binding.Droid.Views.MvxTableLayout | ItemsSource |
MvvmCross.Binding.Droid.Views.MvxFrameControl | DataContext |
MvvmCross.Binding.Droid.Views.MvxDatePicker | Value |
MvvmCross.Binding.Droid.Views.MvxTimePicker | Value |
iOS
Base Control | Default |
---|---|
UIKit.UIButton | TouchUpInside |
UIKit.UIBarButtonItem | Clicked |
UIKit.UISearchBar | Text |
UIKit.UITextField | Text |
UIKit.UITextView | Text |
UIKit.UILabel | Text |
UIKit.UIImageView | Image |
UIKit.UIDatePicker | Date |
UIKit.UISlider | Value |
UIKit.UISwitch | On |
UIKit.UIProgressView | Progress |
UIKit.UISegmentedControl | SelectedSegment |
UIKit.UIPageControl | CurrentPage |
UIKit.UIActivityIndicatorView | Hidden |
MvvmCross.Binding.iOS.Views.MvxCollectionViewSource | ItemsSource |
MvvmCross.Binding.iOS.Views.MvxTableViewSource | ItemsSource |
Mac
Base Control | Default |
---|---|
AppKit.NSButton | Activated |
AppKit.NSButtonCell | Activated |
AppKit.NSSegmentedControl | SelectedSegment |
AppKit.NSSearchField | Text |
AppKit.NSTextField | StringValue |
AppKit.NSTextView | StringValue |
AppKit.NSImageView | Image |
AppKit.NSDatePicker | Date |
AppKit.NSSlider | IntValue |
AppKit.NSMenuItem | Activated |
tvOS
Base Control | Default |
---|---|
UIKit.UIButton | TouchUpInside |
UIKit.UIBarButtonItem | Clicked |
UIKit.UISearchBar | Text |
UIKit.UITextField | Text |
UIKit.UITextView | Text |
UIKit.UILabel | Text |
UIKit.UIImageView | Image |
UIKit.UIProgressView | Progress |
UIKit.UISegmentedControl | SelectedSegment |
UIKit.UIActivityIndicatorView | Hidden |
MvvmCross.Binding.tvOS.Views.MvxCollectionViewSource | ItemsSource |
MvvmCross.Binding.tvOS.Views.MvxTableViewSource | ItemsSource |
The following tables shows all the bindings built into MvvmCross.
When using extension method based bindings you will have to include the relevant using namespace to access the extension methods. Additionally, extension method based bindings are only supported starting with MvvmCross 5.
Android
# MvvmCross 6.x.x
using MvvmCross.Platforms.Android.Binding
# MvvmCross 5.x.x
using MvvmCross.Binding.Droid
Base Control | String | Extension method | Mvx version introduced |
---|---|---|---|
Android.Views.View | Visible | BindVisible() | |
Android.Views.View | Hidden | BindHidden() | |
Android.Views.View | Click | BindClick() | |
Android.Views.View | LongClick | BindLongClick() | |
Android.Views.View | Margin | BindMargin() | 6.0.0 |
Android.Views.View | MarginLeft | BindMarginLeft() | 6.0.0 |
Android.Views.View | MarginRight | BindMarginRight() | 6.0.0 |
Android.Views.View | MarginTop | BindMarginTop() | 6.0.0 |
Android.Views.View | MarginBottom | BindMarginBottom() | 6.0.0 |
Android.Views.View | MarginStart | BindMarginStart() | 6.0.0 |
Android.Views.View | MarginEnd | BindMarginEnd() | 6.0.0 |
Android.Widget.TextView | Text | BindText() | |
Android.Widget.TextView | TextFormatted | BindTextFormatted() | |
Android.Widget.CompoundButton | Checked | BindChecked() | |
Android.Widget.SeekBar | Progress | BindProgress() | |
Android.Widget.ImageView | Bitmap | BindBitmap() | |
Android.Widget.ImageView | Drawable | BindDrawable() | |
Android.Widget.ImageView | DrawableId | BindDrawableId() | |
Android.Widget.ImageView | DrawableName | BindDrawableName() | |
Android.Widget.ImageView | ResourceName | BindResourceName() | |
Android.Widget.ImageView | AssetImagePath | BindAssetImagePath() | |
Android.Widget.EditText | TextFocus | BindTextFocus() | |
Android.Widget.SearchView | Query | BindQuery() | |
Android.Widget.RatingBar | Rating | BindRating() | |
Android.Widget.AdapterView | SelectedItemPosition | BindSelectedItemPosition() | |
Android.Preferences.Preference | Value | BindValue() | |
Android.Preferences.EditTextPreference | Text | BindText() | |
Android.Preferences.ListPreference | Value | BindValue() | |
Android.Preferences.TwoStatePreference | Checked | BindChecked() | |
MvvmCross.Binding.Droid.Views.MvxAutoCompleteTextView | PartialText | BindPartialText() | |
MvvmCross.Binding.Droid.Views.MvxAutoCompleteTextView | SelectedObject | BindSelectedObject() | |
MvvmCross.Binding.Droid.Views.MvxSpinner | SelectedItem | BindSelectedItem() | |
MvvmCross.Binding.Droid.Views.MvxListView | SelectedItem | BindSelectedItem() | |
MvvmCross.Binding.Droid.Views.MvxExpandableListView | SelectedItem | BindSelectedItem() | |
MvvmCross.Binding.Droid.Views.MvxRadioGroup | SelectedItem | BindSelectedItem() |
Android
# MvvmCross 6.x.x
using MvvmCross.Plugin.Color.Platforms.Android.Binding
# MvvmCross 5.x.x
using MvvmCross.Plugins.Color.Droid
Base Control | String | Extension method |
---|---|---|
Android.Widget.TextView | TextColor | BindTextColor() |
Android.Views.View | BackgroundColor | BindBackgroundColor() |
Android - using MvvmCross.Droid.Support.V7.AppCompat
Base Control | String | Extension method |
---|---|---|
Android.Support.V7.Widget.SearchView | Query | BindQuery() |
Android.Support.V7.Widget.Toolbar | Subtitle | BindSubtitle() |
MvvmCross.Droid.Support.V7.AppCompat.Widget.MvxAppCompatAutoCompleteTextView | PartialText | BindPartialText() |
MvvmCross.Droid.Support.V7.AppCompat.Widget.MvxAppCompatAutoCompleteTextView | SelectedObject | BindSelectedObject() |
MvvmCross.Droid.Support.V7.AppCompat.Widget.MvxAppCompatSpinner | SelectedItem | BindSelectedItem() |
MvvmCross.Droid.Support.V7.AppCompat.Widget.MvxAppCompatRadioGroup | SelectedItem | BindSelectedItem() |
Android - using MvvmCross.Droid.Support.V7.Preference
Base Control | String | Extension method | Mvx version introduced ---- | --------- | --------- Android.Support.V7.Preferences.Preference | Value | BindValue() Android.Support.V7.Preferences.ListPreference | Value | BindValue() Android.Support.V7.Preferences.EditTextPreference | Text | BindText() Android.Support.V7.Preferences.TwoStatePreference | Checked | BindChecked() Android.Support.V7.Preferences.Preference | PreferenceClick | BindClick() | 6.2.0
iOS
# MvvmCross 6.x.x
using MvvmCross.Platforms.Ios.Binding
# MvvmCross 5.x.x
using MvvmCross.Binding.iOS
Base Control | String | Extension method |
---|---|---|
UIKit.UIControl | TouchDown | BindTouchDown() |
UIKit.UIControl | TouchDownRepeat | BindTouchDownRepeat() |
UIKit.UIControl | TouchDragInside | BindTouchDragInside() |
UIKit.UIControl | TouchUpInside | BindTouchUpInside() |
UIKit.UIControl | ValueChanged | BindValueChanged() |
UIKit.UIControl | PrimaryActionTriggered | BindPrimaryActionTriggered() |
UIKit.UIControl | EditingDidBegin | BindEditingDidBegin() |
UIKit.UIControl | EditingChanged | BindEditingChanged() |
UIKit.UIControl | EditingDidEnd | BindEditingDidEnd() |
UIKit.UIControl | EditingDidEndOnExit | BindEditingDidEndOnExit() |
UIKit.UIControl | AllTouchEvents | BindAllTouchEvents() |
UIKit.UIControl | AllEditingEvents | BindAllEditingEvents() |
UIKit.UIControl | AllEvents | BindAllEvents() |
UIKit.UIActivityIndicatorView | Hidden | BindHidden() |
UIKit.UISlider | Value | BindValue() |
UIKit.UIStepper | Value | BindValue() |
UIKit.UISegmentedControl | SelectedSegment | BindSelectedSegment() |
UIKit.UIPageControl | CurrentPage | BindCurrentPage() |
UIKit.UIDatePicker | Date | BindDate() |
UIKit.UITextField | ShouldReturn | BindShouldReturn() |
UIKit.UIDatePicker | Time | BindTime() |
UIKit.UILabel | Text | BindText() |
UIKit.UITextField | Text | BindText() |
UIKit.UITextView | Text | BindText() |
UIKit.UISwitch | On | BindOn() |
UIKit.UISearchBar | Text | BindText() |
UIKit.UIButton | Title | BindTitle() |
UIKit.UIButton | DisabledTitle | BindDisabledTitle() |
UIKit.UIButton | HighlightedTitle | BindHighlightedTitle() |
UIKit.UIButton | SelectedTitle | BindSelectedTitle() |
UIKit.UIView | Tap | BindTap() |
UIKit.UIView | Hidden | BindHidden() |
UIKit.UIView | Visible | BindVisible() |
UIKit.UIView | DoubleTap | BindDoubleTap() |
UIKit.UIView | TextFocus | BindTextFocus() |
UIKit.UIView | Visibility | BindVisibility() |
UIKit.UIView | TwoFingerTap | BindTwoFingerTap() |
UIKit.UIView | LayerBorderWidth | BindLayerBorderWidth() |
UIKit.UIBarButtonItem | Clicked | BindClicked() |
Mac
# MvvmCross 6.x.x
using MvvmCross.Platforms.Mac.Binding
# MvvmCross 5.x.x
using MvvmCross.Binding.Mac
Base Control | String | Extension method |
---|---|---|
AppKit.NSView | Visibility | BindVisibility() |
AppKit.NSView | Visible | BindVisible() |
AppKit.NSSlider | IntValue | BindIntValue() |
AppKit.NSSegmentedControl | SelectedSegment | BindSelectedSegment() |
AppKit.NSDatePicker | Time | BindTime() |
AppKit.NSDatePicker | Date | BindDate() |
AppKit.NSTextField | StringValue | BindStringValue() |
AppKit.NSTextView | StringValue | BindStringValue() |
AppKit.NSButton | State | BindState() |
AppKit.NSButton | Title | BindTitle() |
AppKit.NSMenuItem | State | BindState() |
AppKit.NSSearchField | Text | BindText() |
AppKit.NSTabViewController | SelectedTabViewItemIndex | BindSelectedTabViewItemIndex() |
tvOS
# MvvmCross 6.x.x
using MvvmCross.Platforms.Tvos.Binding
# MvvmCross 5.x.x
using MvvmCross.Binding.tvOS
Base Control | String | Extension method |
---|---|---|
UIKit.UIControl | TouchUpInside | BindTouchUpInside() |
UIKit.UIControl | ValueChanged | BindValueChanged() |
UIKit.UIActivityIndicatorView | Hidden | BindHidden() |
UIKit.UISegmentedControl | SelectedSegment | BindSelectedSegment() |
UIKit.UITextField | ShouldReturn | BindShouldReturn() |
UIKit.UILabel | Text | BindText() |
UIKit.UITextField | Text | BindText() |
UIKit.UITextView | Text | BindText() |
UIKit.UISearchBar | Text | BindText() |
UIKit.UIButton | Title | BindTitle() |
UIKit.UIButton | DisabledTitle | BindDisabledTitle() |
UIKit.UIButton | HighlightedTitle | BindHighlightedTitle() |
UIKit.UIButton | SelectedTitle | BindSelectedTitle() |
UIKit.UIView | Tap | BindTap() |
UIKit.UIView | Hidden | BindHidden() |
UIKit.UIView | Visible | BindVisible() |
UIKit.UIView | TextFocus | BindTextFocus() |
UIKit.UIView | DoubleTap | BindDoubleTap() |
UIKit.UIView | Visibility | BindVisibility() |
UIKit.UIView | TwoFingerTap | BindTwoFingerTap() |
UIKit.UIView | LayerBorderWidth | BindLayerBorderWidth() |
UWP
# MvvmCross 6.x.x
using MvvmCross.Platforms.Uap.Binding
# MvvmCross 5.x.x
using MvvmCross.Binding.Uwp
Base Control | String | Extension method |
---|---|---|
Windows.UI.Xaml.FrameworkElement | Visible | BindVisible() |
Windows.UI.Xaml.FrameworkElement | Collapsed | BindCollapsed() |
Windows.UI.Xaml.FrameworkElement | Hidden | BindHidden() |
WPF
# MvvmCross 6.x.x
using MvvmCross.Platforms.Wpf.Binding
# MvvmCross 5.x.x
using MvvmCross.BindingEx.Wpf / using MvvmCross.BindingEx.WindowsCommon
Base Control | String | Extension method |
---|---|---|
Windows.UI.Xaml.FrameworkElement | Visible | BindVisible() |
Windows.UI.Xaml.FrameworkElement | Collapsed | BindCollapsed() |
Windows.UI.Xaml.FrameworkElement | Hidden | BindHidden() |