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.
Related
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");
In Xamarin.Forms 1.3+, how do you make a ContentPage fullscreen?
The most basic exemple of a ContentPage is the one provided upon creating a Xamarin.Forms Portable project.
public App (){
// The root page of your application
MainPage = new ContentPage {
Content = new StackLayout {
VerticalOptions = LayoutOptions.Center,
Children = {
new Label {
XAlign = TextAlignment.Center,
Text = "Welcome to Xamarin Forms!"
}
}
}
};
}
More info (Android): https://developer.android.com/training/system-ui/immersive.html
Your ContentPage is fullscreen. Only the content in your ContentPage does not fill your entire screen.
You can try something like this:
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
your content page is fullscreen . You can check by changing the background color of your content page. Try this following code
BackgroundColor = Color.White
Step 1 in making a full screen is by hiding the Navigation Bar. This can be controlled while Navigating to the View.
FullScreenVideoPlayerPage fullScreenVideoPage = new FullScreenVideoPlayerPage();
NavigationPage.SetHasNavigationBar(fullScreenVideoPage, false);
await Navigation.PushAsync(fullScreenVideoPage);
Remember to use async keyword in Method Signature when using await.
private async void FullScreenVideoPlayerPage_Clicked(object sender, EventArgs e)
Step 2 is to hide the Android Status Bar. But seems this is not standard with Android. I was not fully successful in completely hiding this bar. But I could hide the status icons by:
using Android.App;
using Android.Views;
//......
// Call this method from the constructor after InitializeComponent ();
public void HideStatusBar()
{
var activity = (Activity)Forms.Context;
var window = activity.Window;
var attrs = window.Attributes;
attrs.Flags |= Android.Views.WindowManagerFlags.Fullscreen;
window.Attributes = attrs;
window.ClearFlags(WindowManagerFlags.ForceNotFullscreen);
window.AddFlags(WindowManagerFlags.Fullscreen);
var decorView = window.DecorView;
var uiOptions =
(int)Android.Views.SystemUiFlags.LayoutStable |
(int)Android.Views.SystemUiFlags.LayoutHideNavigation |
(int)Android.Views.SystemUiFlags.LayoutFullscreen |
(int)Android.Views.SystemUiFlags.HideNavigation |
(int)Android.Views.SystemUiFlags.Fullscreen |
(int)Android.Views.SystemUiFlags.Immersive;
decorView.SystemUiVisibility = (Android.Views.StatusBarVisibility)uiOptions;
window.DecorView.SystemUiVisibility = StatusBarVisibility.Hidden;
}
I am trying to populate a listview with a customcell I made. I know how to do it with a tableview, but i have no idea how to do it with a listview. Can someone explain to me please, thank you
Excuse the very rough snippet of code but it is merely to explain the concept behind listviews and databinding.
In my example below I create a listview, assign a custom viewCell and bind the view cell to a model. The model data is populated by XML data retrieved from the server(Code emitted for simplicity). Simpler implementations can be done should you require a listview with hardcoded data.
I suggest reading through the http://developer.xamarin.com/guides/cross-platform/xamarin-forms/introduction-to-xamarin-forms/ and researching the MVVM principle
Additionally the best example code I have found to go through is an enlightening application written by James Montemagno https://github.com/jamesmontemagno/Hanselman.Forms
public TestPage()
{
private ListView listView; // Create a private property with the Type of Listview named listview
listView = new ListView(); //Instantiate the listview
var viewTemplate = new DataTemplate(typeof(CustomViewCell)); //Create a variable for the custom data template
listView.ItemTemplate = viewTemplate; // set the data template to the variable created above
this.Content = new StackLayout()
{
Children = {
listView
}
};
}
Custom View Cell:
public class CustomViewCell : ViewCell
{
public CustomViewCell()
{
Label Test = new Label()
{
Font = Font.BoldSystemFontOfSize(17),
TextColor = Helpers.Color.AppGreen.ToFormsColor(),
BackgroundColor = Color.White
};
var image = new Image();
image.Source = ImageSource.FromFile("testing.png");
Label Test2 = new Label();
tour_Desc.SetBinding(Label.TextProperty, "Test");
round_Desc.SetBinding(Label.TextProperty, "Test2");
var grid = new Grid
{
VerticalOptions = LayoutOptions.FillAndExpand,
RowDefinitions =
{
new RowDefinition {Height = GridLength.Auto },
new RowDefinition {Height = GridLength.Auto },
new RowDefinition {Height = 1}
},
ColumnDefinitions =
{
new ColumnDefinition {Width = GridLength.Auto},
new ColumnDefinition {Width = new GridLength(1, GridUnitType.Star)},
new ColumnDefinition {Width = 80 }
}
,BackgroundColor = Color.White,
};
grid.Children.Add(image, 0, 0);
Grid.SetRowSpan(image, 2);
grid.Children.Add(tour_Desc, 1, 0);
Grid.SetColumnSpan(tour_Desc, 2);
grid.Children.Add(Test, 1, 1);
grid.Children.Add(Test2, 1, 2);
this.View = grid;
}
}
View Model:
public partial class GamesResult
{
public string Test { get; set; }
public string Test1 { get; set; }
}
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.
I've got the following code being called in view the viewdidload method inside of my UIViewController.
Inside the appdelegate I have a UINavigationController which is instantiated with this aforementioned controller and in turn the UINavigationController is placed inside a UITabViewController which in turn is assigned as the rootviewcontroller.
Inside the controller I'm making an async web call to get the data to populate a table, if I use the loading view code to display an activity indicator I get the following warning in monotouch.
Applications are expected to have a root view controller at the end of application launch
public class LoadingView : UIAlertView
{
private UIActivityIndicatorView _activityView;
public void ShowActivity (string title)
{
Title = title;
this.Show();
// Spinner - add after Show() or we have no Bounds.
_activityView = new UIActivityIndicatorView (UIActivityIndicatorViewStyle.WhiteLarge);
_activityView.Frame = new RectangleF ((Bounds.Width / 2) - 15, Bounds.Height - 50, 30, 30);
_activityView.StartAnimating ();
AddSubview (_activityView);
}
public void Hide ()
{
DismissWithClickedButtonIndex (0, true);
}
}
Any pointers would be gratefully received.
EDIT : I'm already setting the root view controller.
window = new UIWindow (UIScreen.MainScreen.Bounds);
window.RootViewController = tabController;
Full appDelegate code :
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
// create a new window instance based on the screen size
window = new UIWindow (UIScreen.MainScreen.Bounds);
tabController = new UITabBarController();
jobsNavigationController = new UINavigationController(new JobsController());
jobsNavigationController.NavigationBar.BarStyle = UIBarStyle.Black;
jobsNavigationController.TabBarItem.Image = UIImage.FromFile("Images/briefcase.png");
jobsNavigationController.TabBarItem.Title = "Current Positions";
myAccountNavigationController = new UINavigationController(new LoginDialogViewController());
myAccountNavigationController.NavigationBar.BarStyle = UIBarStyle.Black;
myAccountNavigationController.TabBarItem.Image = UIImage.FromFile("images/man.png");
myAccountNavigationController.TabBarItem.Title = "My Account";
tabController.SetViewControllers(new UIViewController[] { jobsNavigationController,myAccountNavigationController,new SettingsDialogViewController()},false);
window.RootViewController = tabController;
// make the window visible
window.MakeKeyAndVisible ();
return true;
}
To avoid this warning (in iOS5) and keep iOS 4.x compatibility you can do the following inside your FinishedLaunching method:
if (UIDevice.CurrentDevice.CheckSystemVersion (5, 0))
window.RootViewController = navigation;
else
window.AddSubview (navigation.View);
Look here for a more complete sample.
window.AddSubview(tabcontroller.view);
Fixed the issue, odd I don't set the rootviewcontroller anymore.