How to bind Layout to ViewModel from code? - xamarin.forms

I'm trying to bind items to a StackLayout via this documentation:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/bindable-layouts
I don't use XAML, so I obviously need to bind from inside my code, the documentation shows how to do it this way:
IEnumerable<string> items = ...;
var stack = new StackLayout();
BindableLayout.SetItemsSource(stack, items);
But I need to reference a property from withing my ViewModel, set earlier via the View's BindingContext. Could someone please help me doing this?
Something ala (pseudocode):
var stack = new StackLayout() { ... };
stack.SetBinding(StackLayout.ItemsSource, "Items")
I dont want my controller to know anything about the actual viewModel, and the way its suggested, I need to use it in a typed matter, where I should know the ViewModel.
Below is an example of what I'm trying to accomplish. Please note I use NO XAML at all! Write all my UI in code:
using System;
using System.Collections.Generic;
using Xamarin.Forms;
namespace LayoutTest
{
public class MyPage : ContentPage
{
public MyPage()
{
BindingContext = new MyViewModel();
var layout = new StackLayout();
BindableLayout.SetItemsSource(layout, "?????");
BindableLayout.SetItemTemplate(layout, new DataTemplate(() =>
{
var lbl = new Label();
lbl.SetBinding(Label.TextProperty, "Name");
return lbl;
}));
Content = layout;
}
}
public class MyViewModel
{
List<Item> Items { get; set; }
public MyViewModel()
{
Items = new List<Item>();
Items.Add(new Item { Name = "Kent" });
Items.Add(new Item { Name = "Tony" });
Items.Add(new Item { Name = "Allan" });
}
}
public class Item
{
public string Name { get; set; }
}
}
Dont know what to write in this line:
BindableLayout.SetItemsSource(layout, "?????");
It only take a collection as a property, but then I need to know about the ViewModel, but I dont want that.
What to do?

layout.SetBinding(BindableLayout.ItemsSourceProperty, new Binding("Items”));
Do that instead of the BindableLayout.SetItemsSource that you have now. The binding will use the existing binding context when setting this.

#KenFonager from code behind you could do this as simple as that.
BindingContext = new ViewModels.MainPages.LiveStreamViewModel();
closeButton.SetBinding(Button.CommandProperty, "BackCommand");

Related

Xamarin.Forms Update Label Text From a Service

I have a Label on MainPage.xaml. I can edit Label Text from MainPage.xaml.cs.
There is a foregroung service running as well. There is function in this service to check a value from SQLite DB for every 10 secs. When value changes, Label text should be updated. I tried binding but it is a bit confusing. I manage updating by using like this: (foreground service timer changes App.SomeValue)
protected override void OnAppearing()
{
lblSyncID.Text = App.SomeValue;
}
But I need to see changes without OnAppearing or any other navigation change.
EDIT:
With #Jason's suggestion I used Messaging Center (and also binding) and it works now:
MainPage.xaml:
<Label Text="{Binding AppWaitingRecordValue}" ...
MainPage.xaml.cs:
public partial class MainPage : ContentPage
{
private string appWaitingRecordValue;
public string AppWaitingRecordValue
{
get { return appWaitingRecordValue; }
set
{
appWaitingRecordValue = value;
OnPropertyChanged(nameof(AppWaitingRecordValue));
}
}
public MainPage()
{
InitializeComponent();
BindingContext = this;
AppWaitingRecordValue = "0";
MessagingCenter.Subscribe<App>((App)Application.Current, "AppRecord", (sender) =>
{
AppWaitingRecordValue = App.recordWaiting.ToString();
});
}
.
.
TimestampService.cs (from Project.Android):
// get i from DB
App.recordWaiting = i;
Xamarin.Forms.MessagingCenter.Send<App>((App)Xamarin.Forms.Application.Current, "AppRecord");

CollectionView using DataTemplate is not updating ReactiveContentView BindingContext or ViewModel

I'm using ReactiveUI and trying to get a CollectionView to use a ReactiveContentPage as it's item template in code behind. This works okay until the ReactiveContentView is nested under another Element.
The problem I have is that after scrolling the collection view, the items get duplicated. After debugging, it would seem that the ReactiveContentView's BindingContext or ViewModel aren't updated.
The samples below have been simplified to highlight the issue:
This doesn't work:
public partial class SampleCollectionPage : ReactiveContentPage<SampleCollectionViewModel>
{
private void Build()
{
Content = new CollectionView
{
ItemTemplate = new DataTemplate(() =>
{
return new ContentView
{
Content = new ReactiveContentView<CollectionItemViewModel>
{
Content = new StackLayout
{
Children =
{
new Label()
.Bind(Label.TextProperty, nameof(CollectionItemViewModel.Title)),
new Label()
.Bind(Label.TextProperty, nameof(CollectionItemViewModel.Description)),
new Label()
.Bind(Label.TextProperty, nameof(CollectionItemViewModel.SomeTime)),
}
}
}
};
}),
}
.Bind(CollectionView.ItemsSourceProperty, path: nameof(SampleCollectionViewModel.Items));
}
}
This works:
public partial class SampleCollectionPage : ReactiveContentPage<SampleCollectionViewModel>
{
private void Build()
{
Content = new CollectionView
{
ItemTemplate = new DataTemplate(() =>
{
return new ReactiveContentView<CollectionItemViewModel>
{
Content = new StackLayout
{
Children =
{
new Label()
.Bind(Label.TextProperty, nameof(CollectionItemViewModel.Title)),
new Label()
.Bind(Label.TextProperty, nameof(CollectionItemViewModel.Description)),
new Label()
.Bind(Label.TextProperty, nameof(CollectionItemViewModel.SomeTime)),
}
}
};
}),
}
.Bind(CollectionView.ItemsSourceProperty, path: nameof(SampleCollectionViewModel.Items));
}
}
The only difference being that the working version doesn't put the ReactiveContentView in a ContentView. I'm not sure if I'm missing something or whether this is an bug in Xamarin Forms or ReactiveUI?
(edited to add actual problem: doh)

xamarin binding a button in code to viewModel (without xaml)

I'm using the mvvm approach to develop a barcode scanning app with xamarin. The main hurdle was that the 3rd party scanner object does not work in xaml. I used a ContentPage to create a simple logic-less c# code view which allows me to have a footer with buttons and a logo overlayed at the bottom of the scanner. My problem is that could not find any great best practices for binding items from your code view to your viewModel, as opposed binding a xaml view to a viewModel. Here is some of my view below.
public class BarcodeScannerPage : ContentPage
{
ZXingScannerView zxing;
BarcodeViewModel viewModel;
public BarcodeScannerPage() : base()
{
try
{
viewModel = new BarcodeViewModel();
BindingContext = viewModel;
zxing = new ZXingScannerView
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
Options = new MobileBarcodeScanningOptions
{
TryHarder = true,
DelayBetweenContinuousScans = 3000
},
ScanResultCommand = viewModel.GetResult
};
var cancelButton = new Button
{
BackgroundColor = Color.Gray,
Text = "Cancel",
TextColor = Color.Blue,
FontSize = 15,
Command = viewModel.CancelButton
};
Binding cancelBinding = new Binding
{
Source = viewModel.CancelIsAvailable,
//Path = "ShowCancel",
Mode = BindingMode.OneWay,
};
cancelButton.SetBinding(IsVisibleProperty, cancelBinding);
var doneButton = new Button
{
BackgroundColor = Color.Gray,
Text = "Done",
TextColor = Color.Blue,
FontSize = 15,
Command = viewModel.DoneButton
};
Binding doneBinding = new Binding
{
Source = viewModel.DoneIsAvailable,
//Path = "ShowDone",
Mode = BindingMode.OneWay,
};
doneButton.SetBinding(Button.IsVisibleProperty, doneBinding);
When a barcode is scanned my command, GetResultCommand, sends the result to my BarcodeView model. I have created two Bools in my BarcodeView model named isDoneAvailable and isCancelAvailable. I want to bind these values to the Visibility property of the doneButton and cancelButton in my view. Right now the buttons are bound to whatever the bool values are at the creation of BarcodeViewModel, but they DO NOT update. I need to be able to control visibility from the GetResultCommand method of my BarcodeViewModel. Specifically, when a certain number of barcodes are scanned, I want to make the buttons appear and disappear. I have a feeling they don't update because the path is not set, but when I uncomment the path, the binding doesn't work at all. Any ideas what I've done wrong with the bindings of the buttons, or the correct way to set the Path to my bools in the viewModel? Here is some of my BarcodeViewModel code below.
public class BarcodeViewModel : INotifyPropertyChanged
{
public bool CancelIsAvailable { get { return _cancelIsAvailable; } set { _cancelIsAvailable = value; OnPropertyChanged("ShowCancel"); } }
public bool DoneIsAvailable { get { return _doneIsAvailable; } set { _doneIsAvailable = value; OnPropertyChanged("ShowDone"); } }
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
I still would like to know the correct way to get this binding to update but, I was able to work-around this issue by creating a button in my viewModel and referencing it in my view. Then when I dynamically updated the button in my viewModel, it also updated in my view.

Structuring a MonoTouch.Dialog application

From the examples at Xamarin.com you can build basic M.T. Dialog apps, but how do you build a real life application?
Do you:
1) Create a single DialogViewController and tree every view/RootElement from there or,
2) Create a DialogViewController for every view and use the UINavigationController and push it on as needed?
Depending on your answer, the better response is how? I've built the example task app, so I understand adding elements to a table, click it to go to the 'next' view for editing, but how to click for non-editing? How to click a button, go next view if answer is number 1?
Revised:
There is probably no one right answer, but what I've come up with seems to work for us. Number 2 from above is what was chosen, below is an example of the code as it currently exists. What we did was create a navigation controller in AppDelegate and give access to it throughout the whole application like this:
public partial class AppDelegate : UIApplicationDelegate
{
public UIWindow window { get; private set; }
//< There's a Window property/field which we chose not to bother with
public static AppDelegate Current { get; private set; }
public UINavigationController NavController { get; private set; }
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
Current = this;
window = new UIWindow (UIScreen.MainScreen.Bounds);
NavController = new UINavigationController();
// See About Controller below
DialogViewController about = new AboutController();
NavController.PushViewController(about, true);
window.RootViewController = NavController;
window.MakeKeyAndVisible ();
return true;
}
}
Then every Dialog has a structure like this:
public class AboutController : DialogViewController
{
public delegate void D(AboutController dvc);
public event D ViewLoaded = delegate { };
static About about;
public AboutController()
: base(about = new About())
{
Autorotate = true;
about.SetDialogViewController(this);
}
public override void LoadView()
{
base.LoadView();
ViewLoaded(this);
}
}
public class About : RootElement
{
static AboutModel about = AboutVM.About;
public About()
: base(about.Title)
{
string[] message = about.Text.Split(...);
Add(new Section(){
new AboutMessage(message[0]),
new About_Image(about),
new AboutMessage(message[1]),
});
}
internal void SetDialogViewController(AboutController dvc)
{
var next = new UIBarButtonItem(UIBarButtonSystemItem.Play);
dvc.NavigationItem.RightBarButtonItem = next;
dvc.ViewLoaded += new AboutController.D(dvc_ViewLoaded);
next.Clicked += new System.EventHandler(next_Clicked);
}
void next_Clicked(object sender, System.EventArgs e)
{
// Load next controller
AppDelegate.Current.NavController.PushViewController(new IssuesController(), true);
}
void dvc_ViewLoaded(AboutController dvc)
{
// Swipe location: https://gist.github.com/2884348
dvc.View.Swipe(UISwipeGestureRecognizerDirection.Left).Event +=
delegate { next_Clicked(null, null); };
}
}
Create a sub-class of elements as needed:
public class About_Image : Element, IElementSizing
{
static NSString skey = new NSString("About_Image");
AboutModel about;
UIImage image;
public About_Image(AboutModel about)
: base(string.Empty)
{
this.about = about;
FileInfo imageFile = App.LibraryFile(about.Image ?? "filler.png");
if (imageFile.Exists)
{
float size = 240;
image = UIImage.FromFile(imageFile.FullName);
var resizer = new ImageResizer(image);
resizer.Resize(size, size);
image = resizer.ModifiedImage;
}
}
public override UITableViewCell GetCell(UITableView tv)
{
var cell = tv.DequeueReusableCell(skey);
if (cell == null)
{
cell = new UITableViewCell(UITableViewCellStyle.Default, skey)
{
SelectionStyle = UITableViewCellSelectionStyle.None,
Accessory = UITableViewCellAccessory.None,
};
}
if (null != image)
{
cell.ImageView.ContentMode = UIViewContentMode.Center;
cell.ImageView.Image = image;
}
return cell;
}
public float GetHeight(UITableView tableView, NSIndexPath indexPath)
{
float height = 100;
if (null != image)
height = image.Size.Height;
return height;
}
public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath indexPath)
{
//base.Selected(dvc, tableView, path);
tableView.DeselectRow(indexPath, true);
}
}
#miquel
The current idea of a workflow is an app that starts with a jpg of the Default.png that fades into the first view, with a flow control button(s) that would move to the main app. This view, which I had working previous to M.T.D. (MonoTouch.Dialog), which is a table of text rows with an image. When each row is clicked, it moves to another view that has the row/text in more detail.
The app also supports in-app-purchasing, so if the client wishes to purchase more of the product, then switch to another view to transact the purchase(s). This part was the main reason for switching to M.T.D., as I thought M.T.D. would be perfect for it.
Lastly there would be a settings view to re-enable purchases, etc.
PS How does one know when the app is un-minimized? We would like to show the fade in image again.
I have been asking myself the same questions. I've used the Funq Dependency Injection framework and I create a new DialogViewController for each view. It's effectively the same approach I've used previously developing ASP.NET MVC applications and means I can keep the controller logic nicely separated. I subclass DialogViewController for each view which allows me to pass in to the controller any application data required for that particular controller. I'm not sure if this is the recommended approach but so far it's working for me.
I too have looked at the TweetStation application and I find it a useful reference but the associated documentation specifically says that it isn't trying to be an example of how to structure a MonoTouch application.
I use option 2 that you stated as well, it works pretty nicely as you're able to edit the toolbar options on a per-root-view basis and such.
Option 2 is more feasible, as it also gives you more control on each DialogViewController. It can also helps if you want to conditionally load the view.

Simple databinding IN CODE to a DependencyProperty

My apologies as this is simplistic enough I know the question's been answered but in 30 or so pages, I've yet to find the boiled down problem I'm trying to solve.
I'm not yet well practiced in SL and trying a simple version of attempting to write a TextBox that binds to a property within the screen and updates it when Text is altered and vice versa (property change propagates to the Text). Due to a few reasons, I need to do this with DependencyProperties and in the codebehind rather than INotifyPropertyChanged and in XAML.
My latest attempts look something like this:
public partial class MainPage : UserControl
{
static MainPage()
{
TargetTextProperty = DependencyProperty.Register("TargetText", typeof(string), typeof(MainPage), new PropertyMetadata(new PropertyChangedCallback(TextChanged)));
}
public readonly static DependencyProperty TargetTextProperty;
public string TargetText
{
get { return (string)GetValue(TargetTextProperty); }
set { SetValue(TargetTextProperty, value); }
}
public MainPage()
{
InitializeComponent();
TargetText = "testing";
textBox1.DataContext = TargetText;
Binding ResetBinding = new Binding("TargetText");
ResetBinding.Mode = BindingMode.TwoWay;
ResetBinding.Source = TargetText;
textBox1.SetBinding(TextBox.TextProperty, ResetBinding);
}
private static void TextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MainPage pg = (MainPage)sender;
pg.textBox1.Text = e.NewValue as string;
}
}
Anyone see what (painfully obvious thing?) I'm missing?
Thanks,
John
The following should be enough to set the binding you want:
textBox1.SetBinding(TextBox.TextProperty, new Binding() { Path = "TargetText", Source = this });
The problem with your code is that you set both Source and binding Path to the TargetText property and as a result you get the framework trying to bind to TargetText.TargetText, which is obviously wrong.

Resources