Does mvvmcross natively support binding to nested properties?
For example I have a view model as follows:
class MainViewModel : MvxViewModel
{
public SubViewModelBase SubViewModel
{
get { return _subViewModel; }
set { _subViewModel = value; RaisePropertyChanged(() => SubViewModel); }
}
}
The sub view model may change, but the MainView will bind to the same properties for ALL SubViewModelBase classes... an example SubViewModelBase class as follows:
class SubViewModelBase : MvxViewModel
{
public bool ShowIndeterminantProgress
{
get { return _showIndeterminantProgress; }
set { _showIndeterminantProgress = value; RaisePropertyChanged(() => ShowIndeterminantProgress);}
}
// ... More common properties ...
}
So the MainView would ideally bind like this
this.CreateBinding(_progressBar)
.For(view=> view.Visibility)
.To<MainViewModel>(vm => vm.SubViewModel.ShowIndeterminantProgress)
.WithConversion("Visibility")
.Apply();
Should this work? It doesn't appear to be working, but there are no binding errors in the output?
Does mvvmcross natively support binding to nested properties?
Yes
Should this work?
Yes
For example, this line in ApiExamples for Xamarin.iOS is working:
set.Bind(errorLabel2).To(vm => vm.Errors.Count);
https://github.com/MvvmCross/MvvmCross-Tutorials/blob/master/ApiExamples/ApiExamples.Touch/Views/FirstView.cs#L361
The set of supported functionality is described in https://github.com/MvvmCross/MvvmCross/wiki/Databinding#fluent - but admittedly this fluent binding is more established/used in Xamarin.iOS than it is in Wpf.
To try to debug why your current binding might not be working try adding a simple property to your view which provides debug output
private Visibility _mockVisibility;
public Visibility MockVisibility
{
get
{
return _mockVisibility;
}
set
{
Debug.WriteLine("MockVisibility called with " + value);
_mockVisibility = value;
}
}
and binding this as:
this.CreateBinding(this)
.For(view=> view.MockVisibility)
.To<MainViewModel>(vm => vm.SubViewModel.ShowIndeterminantProgress)
.WithConversion("Visibility")
.Apply();
This should give you debug/trace output and should give you somewhere to put a breakpoint to understand the call stack a little too (although expect this to contain lots of reflection which can be hard to read through).
Beyond this:
you could also try binding a label's text to see what that displays.
you can also try setting the binding trace level to Diagnostic (using the static field MvxBindingTrace.TraceBindingLevel https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.Binding/MvxBindingTrace.cs#L14)
try isolating the problem piece by piece (isolating the converter, isolating the nested property, etc, etc) - I guess this is what you are already doing in asking this question.
Related
I am using MVVM with Galasoft MVVMLight libraries.
I have two models; each has a boolean property and different properties of the same type.
public class Model1 : ObservableObject
{
public EnumPair<YesNoInherit> Model1Property
{
get { return _model1Property; }
set
{
_model1Property = value;
Updated = true
RaisePropertyChanged("Model1Property");
}
}
public bool Updated
{
get { return _updated; }
set
{
_updated = value;
RaisePropertyChanged("Updated");
}
}
}
public class Model2 : ObservableObject
{
public EnumPair<YesNoInherit> Model2Property
{
get { return _model2Property; }
set
{
_model2Property = value;
Updated = true
RaisePropertyChanged("Model2Property");
}
}
public bool Updated
{
get { return _updated; }
set
{
_updated = value;
RaisePropertyChanged("Updated");
}
}
}
The type YesNoInherit is an enum having values No, Yes, and Inherit.
Here is the EnumPair class.
public class EnumPair<T> : ObservableObject where T : struct, IConvertible
{
public T EnumValue
{
get { return _enumValue; }
set
{
if (Type.Equals(value, _enumValue) == false)
{
_enumValue = value;
RaisePropertyChanged();
}
}
}
public string SourceName
{
get { return _sourceName; }
set
{
_sourceName = value;
RaisePropertyChanged();
}
}
}
In my view, I am trying to use a ComboBox to let the user select one of the three enum values, and, in some cases, display custom text. The resource "enumComboBoxTemplate" allows the ComboBox drop-down to show enum descriptions. The converter "inheritanceEnum2Desc" is where the custom text would be applied. "object1" is an instance of "Model1".
<ComboBox ItemTemplate=ItemTemplate="{StaticResource enumComboBoxTemplate}"
EnumSource="enums:YesNoInherit">
<ComboBox.Text>
<MultiBinding Converter="{StaticResource inheritanceEnum2Desc}">
<Binding Path="object1.EnumValue"/>
<Binding Path="object1.SourceName"/>
</MultiBinding>
</ComboBox.Text>
</ComboBox>
"Model2" would be used in future programming employing similar functionality, but with different data.
When I change the selection in the ComboBox, I want to change the value of "Updated" (from false to true) so I can enable a button in the view. This appears to require that the EnumPair class somehow make the program execute the setter for Model1Property. Since the two model classes have properties of type EnumPair, I don't believe I can add any code in EnumPair specific to either model class.
How can I accomplish this? I would greatly appreciate any assistance.
Basically, you have two options: either use some kind of message-bus to update the other model (Prism has EventAggregator, not sure about MVVMLight) or make both model instances forward their properties to a common data source that notifies all of its users when a property changes.
If you want to be able to easily change from one class to another in the future without rewriting all of your code, you need to create an Interface that defines all of the things that the two models have in common, and both model classes need to implement the interface. You could call the interface IModel1
So, instead of having a "Model1" in your viewmodel, you would have an "IModel1" in your viewmodel instead. You could pass in the same object you are passing in now, which is of type Model1, because it implements the IModel1 interface. When you are ready to switch, pass in a Model2 instead, and it will work without having to rewrite anything in your view or viewmodel. Your setters can be completely different - as long as both models have all of the methods and properties that are required by the interface, you will be OK.
Alternately, if Model2 is exactly like Model1 except that it has "extra stuff," you can make Model2 a derived class which derives from Model1.
Google searching either of those terms should point you toward a good tutorial.
May be my formulating of this question is incorrect ("How to bind to get-only property of API class instace?") but here's my problem:
I'm creating powerpoint vsto add-in and I need to bind the SlideIndex property of concrete slide to textbox on the windows form. SlideIndex property has only get accessor. I found that in case of binding I need to use mvvm. According to examples on mvvm theme I installed MvvmLightLibs to my solution from NuGet Packege Manager and desided to "wrap" slide object with this way:
public class SlideWraper: ViewModelBase
{
private PowerPoint.Slide Sld;
public int SlideIndex
{
get
{
return Sld.SlideIndex;
}
set
{
RaisePropertyChanged(() => Sld.SlideIndex);
}
}
public SlideWraper(PowerPoint.Slide sld)
{
Sld=sld;
}
}
Here's my code of binding creation:
...
PowerPoint.Slide ConcreteSlide=this.Application.ActivePresentation.
Slides.FindBySlideID(257);
SlideWraper MyWraper=new SlideWraper(ConcreteSlide);
MyTextBox.DataBindings.Add(new Binding("Text", MyWraper, "SlideIndex"));
...
But this realization fills textbox with correct slide index only at the start of the program. When I replace slide (slide index changed) MyTextBox.Text is not changed.
How can I bind to get-only property of slide?
A few options here. If PowerPoint.Slide supports INPC then you should expose it directly...
private PowerPoint.Slide Sld;
public PowerPoint.Slide Slide
{
get {return Sld;}
set {this.Sld = value; RaisePropertyChanged(() => Slide);}
}
... and then in XAML bind to Slide.SlideIndex.
If PowerPoint.Slide doesn't support INPC then you'll need to create a regular int property in your view model and arrange for PowerPoint.Slide to update that property in response to its SlideIndex value changing.
Finally if you know that SlideIndex has just updated then you can just call `RaisePropertyChanged("Slide")', this can be slow because it will cause all bindings to the Slide properties to update, but sometimes it's the only choice.
Either way, automatic updates can't just magically work by themselves, PowerPoint.Slide needs to have some mechanism to notify the rest of the program that its SlideIndex property has changed.
In a MVP applicaiton if I want to encapsulate a ListBox in a public property so that I could expose the property through an interface to the presenter. I should be able to update the items in the ListBox though this public property. I've tried in several ways to do this
public BindingSource Permission
{
get { return lstGivenPermissions.DataSource; } // Casting error
set { lstGivenPermissions.DataSource = value; }
}
I tried several types for the property like IEnumereble<>, List<> etc. but always either setter or geter shows a casting error.
One option is to have separate properties for get and set.
public ListBox gettingPermission
{
get {return lstGivenPermissions; }
}
public BindingSource Permission
{
set { lstGivenPermissions.DataSource = value; }
}
Is it possible to use a single property in this case or else having two properties is a acceptable solution?
EDIT : I'm using MVP pattern and my requirement is that my presenters are talking to the Views through interfaces. So that if I want one of my presenters to access controllers (like text boxes) in the View, those controllers should be encapsulated in properties. So that I can expose through the interface.
This solved my problem.
public List<string> GivenPermission
{
get { return lstGivenPermissions.Items.Cast<string>().ToList(); }
set { lstGivenPermissions.DataSource = value; }
}
Out of curiosity, is there a way to do this quicker without defining two string or objects?
Xaml
<TextBox Margin="5" Width="100" Text={Binding Path=dataString}></TextBox>
View Model
string _dataString;
public string dataString
{
get
{
return _dataString;
}
set
{
_dataString = value;
base.OnPropertyChanged();
}
}
You can define helpers to shorten the syntax somewhat. For example, if you use the MVVM Light Toolkit, and inherit your ViewModel from ViewModelBase, the toolkit provides a helper that enables use of the following syntax:
private string _dataString = null;
public string DataString
{
get { return _dataString; }
set { Set(ref _dataString, value); }
}
You still have to provide a backing field, but the helper takes care of notifying the exact property that changed, and only raises the event if the new value is in fact different from the current one.
You can also speed up the process of adding the properties by creating a custom code snippet and importing it into Visual Studio via the Code Snippets Manager.
My Database has a Table Foo with the Property IsTrue.
I made a Linq2SQL Class "myData.dbml" for this Database and added the generated Foo-Class to my Datasources bar.
I have a Simple Winforms Window with a ToggleButton which should bind to the inverse of IsTrue.
To achieve this I tried to add the Property NotTrue to the myData.dbml but this caused an SQLException, invalid row name "NotTrue".
I tried to add the Property Code to the myData.cs like this:
public bool NotTrue {
get {
return !this._IsTrue;
}
set {
if (this._IsTrue == value) {
this.OnIsTrueChanging(!value);
this.SendPropertyChanging();
this._IsTrue = !value;
this.SendPropertyChanged("IsTrue");
this.SendPropertyChanged("NotTrue");
this.OnIsTrueChanged();
}
}
}
But then the Property didnt appear when adding Foo to Datasources even when adding [Bindable(true)]
What worked, was binding to IsTrue and negating the Binding with this:
private void InvertBoolBinding(ControlBindingsCollection collection,string property) {
foreach (Binding b in collection) {
if (b.PropertyName.Equals(property)) {
ConvertEventHandler invert = new ConvertEventHandler((sender, e) => { e.Value = !((bool)e.Value); });
b.Format += invert;
b.Parse += invert;
}
}
}
Called like this:
InvertBoolBinding(lockToggleButton.DataBindings, "IsChecked");
Though in my main project there's a huge amount of those Bindings, this approach is quite a hassle. It seems that Datasources isn't able to connect properties through partial classes. When I add the property to the myData.designer.cs, it is found. But this class get regularly regenerated and my property gets lost.
So maybe I need a completely different approach or maybe theres a way to make partial class extensions work.
I hope you got some idea on that. Thanks!
Moving the Linq2SQL class to a different assembly makes Visual Studio resolve the partial connection.
Found here.