Binding a button to a different view model - xamarin.ios

I have a button in View "A" which already has a bindingSet attached to it (it binds to ViewModel "A"). I have button though which needs to be bound to ViewModel "B".
What is the best way to do this?

Your ViewModel is your Model for your View.
If that ViewModel is made up of parts, then that can be done by aggregation - by having your ViewModel made up of lots of sub-models - e.g:
// simplified pseudo-code (add INPC to taste)
public class MyViewModel
{
public MainPartViewModel A {get;set;}
public SubPartViewModel B {get;set;}
public string Direct {get;set;}
}
With this done, then a view component can be bound to direct sub properties as well as sub properties of sub view models:
set.Bind(button).For("Title").To(vm => vm.Direct);
set.Bind(button).For("TouchUpInside").To(vm => vm.A.GoCommand);
set.Bind(button).For("Hidden").To(vm => vm.B.ShouldHideThings);
As long as each part supports INotifyPropertyChanged then data-binding should "just work" in this situation.
If that approach doesn't work for you... In mvvmcross, you could set up a nested class within the View that implemented IMvxBindingContextOwner and which provided a secondary binding context for your View... something like:
public sealed class Nested : IMvxBindingContextOwner, IDisposable {
public Nested() { _bindingContext = new MvxBindingContext(); }
public void Dispose() {
_bindingContext.Dispose();
}
private MvxBindingContext _bindingContext;
public IMvxBindingContext BindingContext { get { return _bindingContext; } }
public Thing ViewModel {
get { return (Thing)_bindingContext.DataContext; }
set { _bindingContext.DataContext = value; }
}
}
This could then be used as something like:
_myNested = new Nested();
_myNested.ViewModel = /* set the "B" ViewModel here */
var set2 = _myNested.CreateBindingSet<Nested, Thing>();
// make calls to set2.Bind() here
set2.Apply();
Notes:
I've not run this pseudo-code, but it feels like it should work...
to get this fully working, you will also want to call Dispose on the Nested when Dispose is fired on your View
given that Views and ViewModels are normally written 1:1 I think this approach is probably going to be harder to code and to understand later.

Related

Binding to Properties of Nested CustomViews in a ViewController

Given I have the following setup (simplified version, removed logic to add to parent view and constraints etc).
public class TestViewModel : MvxViewModel
{
string _text;
public string Text
{
get => _text;
set
{
_text = value;
RaisePropertyChanged(() => Text);
}
}
}
public class TestViewController : MvxViewController<TestViewModel>
{
CustomViewA customViewA;
public TestViewController()
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var bindingSet = this.CreateBindingSet<TestViewController, TestViewModel>();
bindingSet
.Bind(customViewA)
.For(v => v.Text)
.To(vm => vm.Text);
bindingSet.Apply();
}
}
public class CustomViewA : UIView
{
CustomViewB customViewB;
public string Text
{
get => customViewB.Text;
set => customViewB.Text = value;
}
}
public class CustomViewB : UIView
{
UITextField textField;
public string Text
{
get => textField.Text;
set => textField.Text = value;
}
}
Why is it that the bindings do not work? Only if I would make the UITextField in CustomViewB public and directly bind to it in the ViewController rather than the public property that directs to the Text property it seems to work. Like so:
bindingSet
.Bind(customViewA.customViewB.textField)
.For(v => v.Text)
.To(vm => vm.Text);
What am I missing here?
It depends on the requirements you have.
Binding in one direction should work (view model-to-view), I have tested your code and when the ViewModel property changes, the change is propagated to CustomViewA and from there to CusomViewB and finally to the UITextField.
However, the problem is with the opposite direction (view-to-view model). When the user updates the text field, its Text property changes. However, there is nothing notified about this change.
Although the property Text points to the text field, it is not "bound" to it, so when TextField's Text changes, the property itself doesn't know about it and neither does the MvvmCross binding.
In fact, MvvmCross binding in the control-to-view model direction is based on the ability to observe an event that tells the binding to check the new value of the bining source. This is already implemented for UITextField's Text, and it hooks up the EditingChanged event (see source code).
You can still make custom bindings work in the view-to-view model direction by implementing them manually. This is described in the documentation.

Make property in parent object visible to any of its contained objects

I have a class CalculationManager which is instantiated by a BackgroundWorker and as such has a CancellationRequested property.
This CalculationManager has an Execute() method which instantiates some different Calculation private classes with their own Execute() methods which by their turn might or might not instantiate some SubCalculation private classes, in sort of a "work breakdown structure" fashion where each subclass implements a part of a sequential calculation.
What I need to do is to make every of these classes to check, inside the loops of their Execute() methods (which are different from one another) if some "global" CancellationRequested has been set to true. I put "global" in quotes because this property would be in the scope of the topmost CalculationManager class.
So, question is:
How can I make a property in a class visible to every (possibly nested) of its children?
or put down another way:
How can I make a class check for a property in the "root object" of its parent hierarchy? (well, not quite, since CalculationManager will also have a parent, but you got the general idea.
I would like to use some sort of AttachedProperty, but these classes are domain objects inside a class library, having nothing to do with WPF or XAML and such.
Something like this ?
public interface IInjectable {
ICancelStatus Status { get; }
}
public interface ICancelStatus {
bool CancellationRequested { get; }
}
public class CalculationManager {
private IInjectable _injectable;
private SubCalculation _sub;
public CalculationManager(IInjectable injectable) {
_injectable = injectable;
_sub = new SubCalculation(injectable);
}
public void Execute() {}
}
public class SubCalculation {
private IInjectable _injectable;
public SubCalculation(IInjectable injectable) {
_injectable = injectable;
}
}
private class CancelStatus : ICancelStatus {
public bool CancellationRequested { get; set;}
}
var status = new CancelStatus();
var manager = new CalculationManager(status);
manager.Execute();
// once you set status.CancellationRequested it will be immediatly visible to all
// classes into which you've injected the IInjectable instance

Xamarin.ios initialize UIView

I am using Xamarin.iOS. I have created UIView with a few UITextFields. I am looking for best way to initialize text value in these textfields from code.
I can pass text data in the constructor of UIViewContoller, but I don't have access to textFields inside it (they are null). I can change text value of textFields in viewDidLoad method.
I don't want to create additional fields in controller class to store data passed by constructor and use them in viewDidLoad. Do you know better solution ?
I don't want to create additional fields in controller class to store
data passed by constructor and use them in viewDidLoad.
But that's how it's meant to be done.
Alternatively, you can create less fields/properties in your viewcontroller if you use a MVVM pattern:
public class UserViewModel {
public string Name { get; set;}
public string Title { get; set;}
}
public class UserViewController : UIViewController
{
UserViewModel viewModel;
public UserViewController (UserViewModel viewModel) : base (...)
{
this.viewModel = viewModel;
}
public override void ViewDidLoad ()
{
userName.Text = viewModel.Name;
userTitle.Text = viewModel.Title;
}
}
That's the kind of pattern which gives you a lot of code reuse accross platforms (android, WP, ...) and clearly separate concerns. It's a (very) little bit of extra code, but it's worth every byte.

Object creation events in ServiceStack's OrmLite

I need to set an event handler on objects that get instantiated by OrmLite, and can't figure out a good way to do it short of visiting every Get method in a repo (which obviously is not a good way).
To give some background - say I have a class User, which is pulled from database; it also implements INotifyPropertyChanged. I want to assign a handler to that event. Having it auto-populated from Funq would be ideal, but of course OrmLite doesn't ask Funq to hydrate the new object.
So I'm stuck.
Any hints in a right direction would be appreciated.
It sounds to me like you're mixing in presentation logic with your data access logic. If I was in your position I would not attempt to implement INotifyPropertyChanged on a model (such as your User class). Instead I would create a ViewModel and place the databinding logic there (MVVM Style).
Having INotifyPropertyChanged on the data model is not quite logical when you get down to it. If I were to update the database record it would not fire this event for example (but the property has changed). It makes a lot more sense on a ViewModel.
Beyond solving your original issue it also makes building complex screens a lot easier by letting you aggregate, compose, and filter data for display purposes. If you need to pull in information from your database, a RSS feed, a stock ticker web API, and twitter you can do so in your ViewModel.
public class User
{
[AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
}
public class UserViewModel : INotifyPropertyChanged
{
private string _name;
public UserViewModel(User user)
{
_name = user.Name;
}
public string Name
{
get { return _name; }
set {
if (value == _name) return;
_name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Small Note: This answer was written in the context of display data on a screen with a ViewModel, however, the same concept applies to observing model changes for any purpose.

Managed Beans and TabContainer

I ask this squestion on behalf of one of my developers. Haven't looked into details myself.
Assume you have a simple managed bean (=contact) This bean has a method to get the contacts firstName.
I can open an xpage and bind the bean to a computedText Field #{contact.firstName}
In our application we use a tabContainer to have multiple documents of the same type ( contact ) open.
How do I have to use my bean in the container?
faces-config.xml:
<managed-bean>
<managed-bean-name>person</managed-bean-name>
<managed-bean-class>com.package.Person</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
Java Bean Class:
public class Person implements Serializable {
private String strDocumentID;
private Document docData;
private String strFirstName;
private String strLastName;
private static final long serialVersionUID = 2934723410254681213L;
public Person() {
//setting the DocumentUniqueID of the current in a tab opened document
//attention: there could be more than one open tab, all with different documents
//and even different document types; and it is possible to switch back and forth between tabs
//DocumentId = ???;
//Setting the values from the stored document to the object
//setValues();
}
private void setValues() {
try {
Session session=NotesContext.getCurrent().getCurrentSession();
Database currdb=session.getCurrentDatabase();
docData=currdb.getDocumentByUNID(DocumentId);
setStrFirstName(docData.getItemValueString("FirstName"));
setStrLastName(docData.getItemValueString("LastName"));
} catch (NotesException e) {
throw new FacesException("Could not open document for documentId "+ DocumentId, e);
}
}
public Document getDataDocument() {
return docData;
}
public void setDataDocument(Document docData) {
this.docData = docData;
}
public String getDocumentId() {
return DocumentId;
}
public void setDocumentId(String documentId) {
DocumentId = documentId;
}
public String getStrFirstName() {
return strFirstName;
}
public void setStrFirstName(String strFirstName) {
this.strFirstName = strFirstName;
}
public String getStrLastName() {
return strLastName;
}
public void setStrLastName(String strLastName) {
this.strLastName = strLastName;
}
}
Custom Control with computed field:
person.strFirstName
So, the problem is the constructor of the Person Class. It needs to get the "link" to the opened document when the document is opened in a tab and everytime when switched back to this tab. And this without the use of any Data source, because this is what should be done by the managed bean itself.
So, hope that helped to get a little bit more understanding of the problem.
If not, please ask again.
My advice:
make another meta bean implementing map interface. Alter its getter to instantiate and return your data bean. Binding may be then:
meta[someparamwithunid].field
And save would be:
meta[someparamwithunid].setValues()
Like this:
public class People implments java.util.Map {
Map<String,Person> people = new HashMap<String,Person>();
public Person get(String unid) {
if people.keySet().contains(unid) {
return people.get(unid)
} else {
// make instance and store it in people map, return it
}
// implement other methods
}
With view scope I think there is no problem with concurrency.
Frantisek points into the right direction. Your request bean would not be a person bean, but a people bean. You then can use an expression like
#{people[index].name}
to refer to a specific person. People would be the managed bean and the index could either be the UNID or the tab number. I find the later one easier to implement. you need to have a loadPerson(index) = UNID function to load an existing person. More information on the use of Expression language can be found here:
Sun Oracle JSF documentation or in some Course materials.
Hope that helps.
I'm not sure if this bean will work in the requestScope because you have probably a lot of partial refreshes with the tabcontainer (maybe try change it to a higher level scope).

Resources