MVVM Light TextView two-way binding in Xamarin (Android) - binding

Has anyone successfully implemented a two-way binding on a TextView with MVVM Light? Two-way works perfectly fine with EditView, but the moment I try two-way binding with TextView - only one way binding works. Does anyone have any insight as to why, please?
View Model:
private string _someField;
public string SomeField
{
get { return _someField; }
set { Set(ref _someField, value); }
}
View:
private EditText _editableText;
public EditText EditableText;
{
get { return _editableTex ?? (_editableTex = FindViewById<EditText>(Resource.Id.editText1)); }
}
private TextView _simpleText
public TextView SimpleText
{
get { return _simpleText ?? (_simpleText = FindViewById<TextView>(Resource.Id.textDateDisplay)); }
}
protected override void OnCreate(Bundle savedInstanceState)
{
bindings.Add(this.SetBinding(() => vm.SomeField, () => EditableText.Text, BindingMode.TwoWay));
bindings.Add(this.SetBinding(() => vm.SomeField, () => SimpleText.Text, BindingMode.TwoWay));
}
No errors are thrown. But when I change (in code) of the View the text of the EditableText (EditableText.Text="asdf";) the corresponding set { Set(ref _someField, value); } triggers in the VewModel. It also triggers, naturally, if I just type in the EditText widget.
However, when I change (in code) the text property of the SimpleText (SimpleText.Text="2145";) it does not trigger the corresponding set.
Does anyone know why?
Thank you very much for help,
mike

You can't set a two-way binding with TextViews, because they doesn't allow input by user.
When you are using MVVM pattern you should never update the view. You should update the bound model property. So you should replace SimpleText.Text = "2145"; with vm.SomeField = "2145";.

Related

MVVMCross ListView - how to populate only with the list items matches certain conditions?

MVVMCross ListView - How to display a list of items in a listview grouped by a status? For exampled activate or deactivated
I have added two listview to the layout and set the datasource to have two lists (activatedItems and deactivatedItems => both of them derived from ListOfItems. ) using the xaml data binding with now code behind. But had a problem in the data not updated when there is a change to the underlying list (ListOfItems).
To resolve this, I have to re create the UI by setting the content view whenever there a change to the data set.
This is not an elegant solution and I would like to have one listview with sections "Activated" and "Deactivated". Then on touching the Activated item should get added to the Deactivated list and the UI should reflect the same.
Since you have a LoginItemModel with a Title property (let's assume it's a string property), I would recommend you to extract this to an ILoginItemModel interface and also add a bool IsHeader property. This way:
public interface ILoginItemModel {
public bool IsHeader { get; }
public LoginSection ItemGroup { get; }
public string Title { get; }
}
Make your LoginItemModel extend this ILoginItemModel interface and make IsHeader always return false.
public bool IsHeader => false;
Write a second class extending this same interface, let's call it LoginItemHeaderModel. This class will just have these three properties:
public class LoginItemHeaderModel : ILoginItemModel {
public bool IsHeader => true;
LoginSection _itemGroup;
public LoginSection ItemGroup => _itemGroup;
string _title;
public string Title => _title;
public LoginItemHeaderModel(LoginSection itemGroup, string title) {
_itemGroup = itemGroup;
_title = title;
}
}
Now we have two models that fits into a single IList<ILoginItemModel>.
Back to your view model, you can declare a fourth list property and put it all together along with your new headers:
public List<ILoginItemModel> SectionedLoginItems {
get {
var values = Enum.GetValues(typeof(LoginSection)).Cast<LoginSection>();
List<ILoginItemModel> list = new List<ILoginItemModel>();
foreach (LoginSection loginSection : values) {
list.Add(new LoginItemHeaderModel(loginSection, loginSection.ToString()));
list.AddRange(LoginItems.Where(l => l.ItemGroup == loginSection));
}
return list;
}
}
Now you have your single sorted and with section headers list.
You should now write a new layout for those header items (aren't you going to make them look like the common items, right?) and, in your custom MvxAdapter, inside the GetBindableView method, check whether the object source (which will be an ILoginItemModel object) is a header item or not.
Whenever you make a change to ActiveLoginItems or DeactivatedLoginItems, make a call to RaisePropertyChanged in your ViewModel i.e.
RaisePropertyChanged(() => ActiveLoginItems);
or
RaisePropertyChanged(() => DeactivatedLoginItems);
That should make the MvxListView update with the changes.

MvvmCross: How to recall the current view model again using ICommand?

I tried to do a basic form validation to require some fields on the form must filled in.
I tried to do the Validation check on the ViewModel, I have a ValidateForm function and will return a ValidationMessage, however, when the SaveCommand get call and it won't call back the current View again, so the error message Alert View won't popup. How can I recall the current View Model again after SaveCommand finished?
private string _ValidationMessage;
public string ValidationMessage
{
get { return _ValidationMessage; }
set
{
_ValidationMessage = value;
RaisePropertyChanged(() => ValidationMessage);
}
}
private string ValideForm()
{
if (string.IsNullOrEmpty(FirstName) || string.IsNullOrEmpty(LastName) || string.IsNullOrEmpty(Email) || string.IsNullOrEmpty(ZipCode))
return "Fields with * are required.";
return null;
}
private MvxCommand _saveCommand;
public ICommand SaveCommand
{
get
{
_saveCommand = _saveCommand ?? new MvxCommand(SaveCommandHandler);
return _saveCommand;
}
}
private void SaveCommandHandler()
{
var validationMessage = ValideForm();
if (!string.IsNullOrEmpty(validationMessage))
{
ValidationMessage = validationMessage;
return;
}
ShowViewModel<NextScreenViewModel>();
}
I think this is the same question as MvvmCross Dialog. The poster there suggests several ways to do this, and my answer covers one more 'architectural' way to do it too.
If you'd prefer a simpler way, then you can also just use a string property - eg ErrorMessage. The view can listen for changes in that string. When they happen,
then the view can display an error dialog. This is a bit like the approach taken for Progress Dialogs in this video and code - N=34 - http://slodge.blogspot.co.uk/2013/07/n34-showing-progress-isbusy-display-n1.html

MvvmCross Android Dialog bind programmatically

I want to use the Android.Dialog (Cross.UI) in my MvvmCross project. My first approach was to use AutoViews. As this feature is still fairly young, the alternative was to implement the dialog in touch and Droid platforms.
For now i'm just doing this for Droid and I need to programmatically bind the properties of the ViewModel to the elements of the Dialog.
My View and ViewModel code is the following:
View
public class DialogConfigurationView : MvxBindingDialogActivityView<DialogConfigurationViewModel>
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
DroidResources.Initialise(typeof(Resource.Layout));
Root = new RootElement()
{
new Section("Private Configuration")
{
new EntryElement("Name:"),
new EntryElement("Description:"),
new BooleanElement("Active?")
}
};
}
}
ViewModel
public class DialogConfigurationViewModel : MvxViewModel
{
public ConfigurationSet Configuration
{
get { return _configuration; }
set
{
if (_configuration != value)
{
_configuration = value;
RaisePropertyChanged(() => Configuration);
}
}
}
private ConfigurationSet _configuration;
}
My goal is to have a twoway bind the EntryElement("Name:") with the property ViewModel.Configuration.Name.
Can anyone help me with this? Can this be done?
I don't know if there are any monodroid.dialog mvvmcross samples floating around which don't use autoviews!
However.... the basic syntac for binding should be the same as MonoTouch.Dialog - e.g. something like:
new Section("Contact Info")
{
new StringElement("ID", ViewModel.Customer.ID ?? string.Empty),
new EntryElement("Name", "Name").Bind(this, "{'Value':{'Path':'Customer.Name'}}"),
new EntryElement("Website", "Website").Bind(this, "{'Value':{'Path':'Customer.Website'}}"),
new EntryElement("Primary Phone", "Phone").Bind(this, "{'Value':{'Path':'Customer.PrimaryPhone'}}"),
},
new Section("Primary Address")
{
new EntryElement("Address").Bind(this, "{'Value':{'Path':'Customer.PrimaryAddress.Street1'}}"),
new EntryElement("Address2").Bind(this, "{'Value':{'Path':'Customer.PrimaryAddress.Street2'}}"),
new EntryElement("City").Bind(this, "{'Value':{'Path':'Customer.PrimaryAddress.City'}}"),
new EntryElement("State").Bind(this, "{'Value':{'Path':'Customer.PrimaryAddress.State'}}"),
new EntryElement("Zip").Bind(this, "{'Value':{'Path':'Customer.PrimaryAddress.Zip'}}"),
},
from https://github.com/slodge/MvvmCross/blob/vnext/Sample%20-%20CustomerManagement/CustomerManagement/CustomerManagement.Touch/Views/BaseCustomerEditView.cs
Note that in MvvmCross bindings for MonoTouch and MonoDroid, the default binding for things like text edit boxes is generally TwoWay by default.
If you do get a sample running, then please feel free to post it to a gist or to a repo - or to blog about it - looks like we could do with some samples to work from!

How to create view's actions listener for MvxItemTemplate

I have a view which contains an MvxListView and a form. I can hide the softkeyboard using the following code in my view's code (as this is pure view concerns)
var editText = FindViewById<EditText>(Resource.Id.newCheckbookName);
editText.EditorAction += (object sender, TextView.EditorActionEventArgs e) =>
{
if (e.ActionId == ImeAction.Done)
{
InputMethodManager inputMgr = GetSystemService(InputMethodService) as InputMethodManager;
inputMgr.HideSoftInputFromWindow(CurrentFocus.WindowToken, 0);
}
return;
};
In my item template I have an other text editor and would like to have the same behavior. But where can I add my code as I don't have any view code for the MwxItemTemplate ?
I think the easy way to do this is to use a custom 'View' within the listitem.
Note: that 'View' here refers to Android Views - not Model-View-ViewModel views - sorry for the naming confusion!
Creating custom views is easy to do...
Just create a custom View - e.g.
namespace Angevelle.App1.UI.Droid.Controls
{
public class MyText : EditText
{
public MyText(Context context, IAttributeSet attrs)
: base(context, attrs)
{
this.EditorAction += OnEditorAction;
}
private void OnEditorAction(object sender, EditorActionEventArgs editorActionEventArgs)
{
if (editorActionEventArgs.ActionId == ImeAction.Done)
{
// this code not tested - but something like this should work
var imm = (InputMethodManager)Context.GetSystemService(Context.InputMethodService);
imm.HideSoftInputFromWindow(WindowToken, 0);
}
}
}
}
Then you can use that View in your AXML just as you do Android or Mvx views:
<angevelle.app1.ui.droid.controls.MyText
android:layout_height=....
/>
If you are finding angevelle.app1.ui.droid.controls too verbose, then you could shorten this using an abbreviation in setup.cs:
protected override IDictionary<string, string> ViewNamespaceAbbreviations
{
get
{
var abbreviations = base.ViewNamespaceAbbreviations;
abbreviations["Abv"] = "angevelle.app1.ui.droid.controls";
return abbreviations;
}
}
then you can just use:
<Abv.MyText
android:layout_height=....
/>
An alternative approach might be to somehow customise the list...
If you ever do need to completely customise a listview and its adapter, then this can be easily done using the same type of technique - inherit from MvxBindableListView in your UI project:
public class MyListView : MvxBindableListView
{
public MyListView(Context context, IAttributeSet attrs);
: base(context, attrs, new MyListAdapter(context))
{
}
}
where MyListAdapter overrides the view creation:
public class MyListAdapter : MvxBindableListAdapter
{
public MyListAdapter(Context context)
: base(context)
{
}
// put your custom Adapter code here... e.g.:
protected override MvxBindableListItemView CreateBindableView(object source, int templateId)
{
return new MySpecialListItemView(_context, _bindingActivity, templateId, source);
}
}
where MySpecialListItemView inherits from MvxBindableListItemView but adds your own custom features.
Using this approach your list would then change from:
<Mvx.MvxBindableListView
....
/>
to:
<Abv.MyListView
....
/>
For more examples of custom views, take a look around GitHub - e.g. at some of the Calendar, ColorPicker, ActionBar projects in https://github.com/Cheesebaron
Don't expect your custom controls to render in the xamarin designer (well, not yet...)
Two final notes...
To reuse code... you might want to put that HideSoftInputFromWindow functionality in an extension method somehow so that you can just call anyEditText.HideOnDone()
Be careful when using the Monodroid/monotouch events on Views/UIViews - these events tend to use the native delegates/listeners - and so sometimes you can find that attaching something to subscribe to one event can unattach something else! Generally you are OK as long as you don't mix and match the C# event subscriptions at the same time as the native listener/delegate handlers.

smartgwt listgrid set cursor to hand over an icon field

I've been working on this problem for quite a while but have not been able to solve it.
I have a listgrid with a field type icon. I would like to change the cursor to "hand" over the icon.
I've been searching the web and saw that a couple of solutions existed.
One of them is using addCellOverHandler for the list grid. But I don't understand how you can change the cursor for the specified field of the listgrid.
this.addCellOverHandler(new CellOverHandler() {
#Override
public void onCellOver(CellOverEvent event) {
// not able to get the field and setCursor()
}
});
My field in the listgrid is defined as:
ListGridField iconField = new ListGridField("icon");
iconField.setAlign(Alignment.CENTER);
iconField.setType(ListGridFieldType.ICON);
iconField.setIcon("icons/icon.gif");
Like someone pointed out on the forum, a setCursor() method exist for the listgrid, but not for the field only...
If anybody has a clue...
Thanks
After some more (a lot more...) googling, I found this:
http://forums.smartclient.com/showthread.php?t=15748
The thing is to Override the getCellStyle method in the listgrid.
Here is the code I use:
#Override
protected String getCellStyle(ListGridRecord record, int rowNum, int colNum) {
if (colNum==6){
return "EC_pointer";
}
return super.getCellStyle(record, rowNum, colNum);
}
and in my CSS file:
.EC_pointer {
cursor: pointer;
}
The major fallout is that you have to know in advance the column number of the field.
Further to my comment and adding information from here I tested the following code which works with SmartGwt2.4 under Firefox 5.0.
demandesGrid.setCanHover(true);
demandesGrid.setShowHover(false);
demandesGrid.addCellHoverHandler(new CellHoverHandler() {
#Override
public void onCellHover(CellHoverEvent event) {
if (event.getColNum() == demandesGrid.getFieldNum("icon")) {
// SC.say(demandesGrid.getChildren()[3].toString());
demandesGrid.getChildren()[3].setCursor(Cursor.POINTER);
} else {
demandesGrid.getChildren()[3].setCursor(Cursor.DEFAULT);
}
}
});
I don't know if the index of the ListGridBody is constant; I found it with the SC.say line.
How about
grid.addCellOverHandler(new CellOverHandler() {
#Override
public void onCellOver(CellOverEvent event) {
//cellOver event to get field and refresh the cell
//grid.refreshCell(i, j);
}
});
The best approach is fully demonstrated here (take a look at how "comments/stats" field is being initialized).
In short, u have to extend ListGrid and override createRecordComponent method. In this method you can make any custom component you like and it will be show in grid cell.
Also ListGrid should be initialized with:
listGrid.setShowRecordComponents(true);
listGrid.setShowRecordComponentsByCell(true);

Resources