I am fairly new at working with the Android code in Visual Studio 2019. The ultimate result desired here was to be able to display the various wifi Access Points near the phone. I have gotten the wifi scan to work and I can see the results of the scan in the "WifiScans" collection but the listWifiScan ListView does not show the results on the display. I can see lines on the display for the number of items that should be displayed. If I touch one of the items I can see it turn to a solid color (Orange) but the information is not displayed. Can anyone tell me what I am doing wrong? Thanks for any help you can give.
using Android;
using Android.Content.PM;
using Android.Support.V4.App;
using Android.Support.V4.Content;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using Xamarin.Forms;
namespace MobileWifi
{
public partial class MainPage : ContentPage
{
ObservableCollection<WifiScan> WifiScans { get; set; }
Label lblTitle;
ListView listWifiScan;
readonly Button btnWifiScan;
public static bool response = false;
public MainPage()
{
// add code to handle the components here
WifiScans = new ObservableCollection<WifiScan>();
this.Padding = new Thickness(20,20,20,20); // wall padding for page
// Listview data template
var scanDataTemplate = new DataTemplate(() =>
{
var grid = new Grid();
var bssidLabel = new Label { FontAttributes = FontAttributes.Bold };
var ssidLabel = new Label();
var levelLabel = new Label { HorizontalTextAlignment = TextAlignment.End };
bssidLabel.SetBinding(Label.TextProperty, "BSSID");
ssidLabel.SetBinding(Label.TextProperty, "SSID");
levelLabel.SetBinding(Label.TextProperty, "Level");
grid.Children.Add(bssidLabel);
grid.Children.Add(ssidLabel, 1, 0);
grid.Children.Add(levelLabel, 2, 0);
return new ViewCell { View = grid };
});
// setup StackLayout for controls and set spacing of controls within the layout
StackLayout panel = new StackLayout
{
Spacing = 15,
Margin = new Thickness(20),
};
panel.Children.Add(lblTitle = new Label
{
Text = "Mobile Wifi Scanner",
HorizontalTextAlignment = TextAlignment.Center,
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
FontAttributes = FontAttributes.Bold,
});
panel.Children.Add(btnWifiScan = new Button
{
Text = "Start Scan"
});
panel.Children.Add(listWifiScan = new ListView
{
ItemsSource = WifiScans,
HasUnevenRows = true,
ItemTemplate = scanDataTemplate,
Margin = new Thickness(0, 20, 0, 0),
});
btnWifiScan.Clicked += OnBtnWifiScanClicked;
this.Content = panel;
}
private void OnBtnWifiScanClicked(object sender, EventArgs e)
{
btnWifiScan.Text = "Scanning...";
WifiScans.Clear(); // clear out results from previous scan
try
{
IWifiScan service = DependencyService.Get<IWifiScan>().GetObj();
service.Start();
service.Finished += WifiScanDone;
}
catch (Exception ex)
{
btnWifiScan.Text = "Start Scan";
DisplayAlert("Alert", ex.Message, "OK");
}
}
public void WifiScanDone(object sender,EventArgs e)
{
IWifiScan service = DependencyService.Get<IWifiScan>();
List<string> WifiBSSID = service.GetBSSID();
List<string> WifiSSID = service.GetSSID();
List<int> WifiLevel = service.GetLevel();
int count = WifiBSSID.Count;
// add logic here to display the data from the scan
// add results of the scan
if(count > 0)
{
for (int i = 0; i < count; i++)
{
WifiScans.Add(new WifiScan { WifiBSSID = WifiBSSID[i], WifiSSID = WifiSSID[i], WifiLevel = WifiLevel[i].ToString()});
}
}else
{
WifiScans.Add(new WifiScan { WifiBSSID = "None Found", WifiSSID = "", WifiLevel = "0"});
}
btnWifiScan.Text = "Start Scan";
// finalize the scan, etc.
service.Done();
}
}
}
I finally found the problem. It turns out I did not specify the correct property in the binding. The correct code for the binding in the data template should have been:
bssidLabel.SetBinding(Label.TextProperty, "WifiBSSID");
ssidLabel.SetBinding(Label.TextProperty, "WifiSSID");
levelLabel.SetBinding(Label.TextProperty, "WifiLevel");
I'm not sure where I got the original values but after studying the data binding properties I realized they were wrong. Also I did not include the source in the original question that would have helped identify the problem so I am adding it here in case it is helpful to someone else that may have a similar problem.
public class WifiScan
{
public string WifiSSID { get; set; }
public string WifiBSSID { get; set; }
public string WifiLevel { get; set; }
}
Related
I'm trying also to generate from a database list the Shell Flyout elements. But I am not sure that implementing directly in AppShell.xaml.cs is a good idea. Is it possible to do the same thing but in
xaml?
I saw something similarly in Define FlyoutItem appearance. But I still can't figure it out.
Also, is it a good idea to implement code directly into AppShell.xaml.cs or App.xaml.cs?
LocationLevelViewModel diudu = new LocationLevelViewModel();
var lists = diudu.Items;
foreach (var list in lists)
{
ShellSection shell_section = new ShellSection();
shell_section.Title = list.LocationName;
shell_section.Icon = "icon_feed.png";
shell_section.Items.Add(new ShellContent() { Content = new LocationLevelPage() });
this.Items.Add(shell_section);
}
The code you provided, you could create a new class named AppShellCS and use teh code in a new AppShellCS.cs file like below. It works well. I made a simple example to test instead of database.
public class AppShellCS : Shell
{
public AppShellCS()
{
LocationLevelViewModel diudu = new LocationLevelViewModel();
//var lists = diudu.Items;
var lists = new List<LocationLevelViewModel>
{
new LocationLevelViewModel() { LocationName="A"},
new LocationLevelViewModel() { LocationName="B"},
new LocationLevelViewModel() { LocationName="C"}
};
foreach (var list in lists)
{
ShellSection shell_section = new ShellSection();
shell_section.Title = list.LocationName;
shell_section.Icon = "tab_feed.png";
shell_section.Items.Add(new ShellContent() { Content = new LocationLevelPage() });
this.Items.Add(shell_section);
}
}
}
public class LocationLevelViewModel
{
public string LocationName { get; set; }
}
App.xaml.cs
public App()
{
InitializeComponent();
DependencyService.Register<MockDataStore>();
MainPage = new AppShellCS();
}
OutPut:
If you want to add this in a exist Shell project, you could try the code below.
AppShell.xaml.cs
public partial class AppShell : Xamarin.Forms.Shell
{
public AppShell()
{
InitializeComponent();
//Routing.RegisterRoute("step", typeof(Page2));
LocationLevelViewModel diudu = new LocationLevelViewModel();
//var lists = diudu.Items;
var lists = new List<LocationLevelViewModel>
{
new LocationLevelViewModel() { LocationName="A"},
new LocationLevelViewModel() { LocationName="B"},
new LocationLevelViewModel() { LocationName="C"}
};
foreach (var list in lists)
{
ShellSection shell_section = new ShellSection();
shell_section.Title = list.LocationName;
shell_section.Icon = "tab_feed.png";
shell_section.Items.Add(new ShellContent() { Content = new LocationLevelPage() });
this.Items.Add(shell_section);
}
}
}
Output:
I just tried to Inserting a data to azure platform. But that problem occurred.
No 'id' member found on type 'app4.MainActivity' I used a break points for examination the problem and it goes to the catch line at " JObject jo = new JObject(); or the next line (now break points doesnt work) "
using Android.App;
using Android.Widget;
using Android.OS;
using Microsoft.WindowsAzure.MobileServices;
using Android.Views;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
namespace App4
{
[Activity(Label = "App4", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : Activity
{
public TextView txtShow;
public EditText txtEnter;
public Button btnSave;
// string id;
[JsonProperty(PropertyName = "id")]
public string Id
{
get { return Id; }
set { Id = value; }
}
[JsonProperty(PropertyName = "Questions")]
public string Questions { get; set; }
/* [JsonProperty(PropertyName = "complete")]
public bool Complete { get; set; }*/
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
Button btnSave = FindViewById<Button>(Resource.Id.btnSave);
TextView txtShow = FindViewById<TextView>(Resource.Id.txtSoru);
EditText txtEnter = FindViewById<EditText>(Resource.Id.txtSoruGir);
Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();
SQLitePCL.Batteries.Init();
// IMobileServiceTable<MainActivity> SurveyTable = client.GetTable<MainActivity>();
// Initialization for Azure Mobile Apps
Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();
// This MobileServiceClient has been configured to communicate with the Azure Mobile App and
// Azure Gateway using the application url. You're all set to start working with your Mobile App!
Microsoft.WindowsAzure.MobileServices.MobileServiceClient TrendDemoClient = new Microsoft.WindowsAzure.MobileServices.MobileServiceClient(
"http://xxxx.azurewebsites.net");
// Set our view from the "main" layout resource
// SetContentView (Resource.Layout.Main);
}
// It is save button. ( or insert button)using on xml
// using via android:onClick="AddItem"
[Java.Interop.Export()]
public async void AddItem(View view)
{
var client = new MobileServiceClient("http://*****.azurewebsites.net/");
IMobileServiceTable SurveyTable = client.GetTable("Survey");
var table = client.GetSyncTable<MainActivity>();
txtEnter.Text = txtShow.Text;
string txt;
txt = txtShow.Text;
var item = new MainActivity
{
Questions = txtShow.Text,
};
Questions = txt;
try
{
////the problem occur over here and goes to catch
JObject jo = new JObject();
jo.Add("id", "myemail#emaildomain.com");
jo.Add("Questions", "Hello World");
// jo.Add("Complete", false);
//// It does't come so far
var inserted = await table.InsertAsync(jo);
// var inserted = await table.InsertAsync(Questions);
}
catch (Exception e)
{
CreateAndShowDialog(e, "Amk Necosu");
}
}
private void CreateAndShowDialog(Exception exception, String title)
{
CreateAndShowDialog(exception.Message, title);
}
private void CreateAndShowDialog(string message, string title)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetMessage(message);
builder.SetTitle(title);
builder.Create().Show();
}
}
}
Backend Part is here
Controller.cs
using System.Linq;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.OData;
using Microsoft.Azure.Mobile.Server;
using TrendDemoService.DataObjects;
using TrendDemoService.Models;
using Microsoft.Azure.Mobile.Server.Config;
namespace TrendDemoService.Controllers
{
[MobileAppController]
public class Controller : TableController<MyFirstDbTableModel>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
Context context = new Context();
DomainManager = new EntityDomainManager<MyFirstDbTableModel>(context, Request);
}
// GET tables/TodoItem
public IQueryable<MyFirstDbTableModel> GetAllMyFirstDbTableModels()
{
return Query();
}
// GET tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<MyFirstDbTableModel> GetMyFirstDbTableModel(string id)
{
return Lookup(id);
}
// PATCH tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<MyFirstDbTableModel> PatchMyFirstDbTableModel(string id, Delta<MyFirstDbTableModel> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/TodoItem
public async Task<IHttpActionResult> PostMyFirstDbTableModel(MyFirstDbTableModel item)
{
MyFirstDbTableModel current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteTodoItem(string id)
{
return DeleteAsync(id);
}
}
}
Context.cs
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
using Microsoft.Azure.Mobile.Server;
using Microsoft.Azure.Mobile.Server.Tables;
using TrendDemoService.DataObjects;
namespace TrendDemoService.Models
{
public class Context : DbContext
{
// You can add custom code to this file. Changes will not be overwritten.
//
// If you want Entity Framework to alter your database
// automatically whenever you change your model schema, please use data migrations.
// For more information refer to the documentation:
// http://msdn.microsoft.com/en-us/data/jj591621.aspx
private const string connectionStringName = "Name=MS_TableConnectionString";
public Context() : base(connectionStringName)
{
}
public DbSet<MyFirstDbTableModel> MyFirstDbTableModel { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
}
}
}
MyFirstDbTableModel
using Microsoft.Azure.Mobile.Server;
namespace TrendDemoService.DataObjects
{
public class MyFirstDbTableModel : EntityData
{
public string Questions { get; set; }
public bool Complete { get; set; }
}
}
AFTER EDIT
Startup.MobileApp
Backend Part
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Entity;
using System.Web.Http;
using Microsoft.Azure.Mobile.Server;
using Microsoft.Azure.Mobile.Server.Authentication;
using Microsoft.Azure.Mobile.Server.Config;
using TrendDemoService.DataObjects;
using TrendDemoService.Models;
using Owin;
namespace TrendDemoService
{
public partial class Startup
{
public static void ConfigureMobileApp(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
//For more information on Web API tracing, see http://go.microsoft.com/fwlink/?LinkId=620686
config.EnableSystemDiagnosticsTracing();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
// Use Entity Framework Code First to create database tables based on your DbContext
Database.SetInitializer(new TrendDemoInitializer());
// To prevent Entity Framework from modifying your database schema, use a null database initializer
// Database.SetInitializer<TrendDemoContext>(null);
MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();
if (string.IsNullOrEmpty(settings.HostName))
{
// This middleware is intended to be used locally for debugging. By default, HostName will
// only have a value when running in an App Service application.
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
app.UseWebApi(config);
}
}
public class TrendDemoInitializer : CreateDatabaseIfNotExists<Context>
{
protected override void Seed(Context context)
{
List<MyFirstDbTableModel> todoItems = new List<MyFirstDbTableModel>
{
new MyFirstDbTableModel { Id = Guid.NewGuid().ToString(), Questions = "First item", Complete = false },
new MyFirstDbTableModel { Id = Guid.NewGuid().ToString(), Questions = "Second item", Complete = false },
};
foreach (MyFirstDbTableModel todoItem in todoItems)
{
context.Set<MyFirstDbTableModel>().Add(todoItem);
}
base.Seed(context);
}
}
}
Frontend
MainActivity
using Android.App;
using Android.Widget;
using Android.OS;
using Microsoft.WindowsAzure.MobileServices;
using Android.Views;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Runtime.Serialization;
namespace App4
{
[Activity(Label = "App4", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : Activity
{
public TextView txtShow;
public EditText txtEnter;
public Button btnSave;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
Button btnSave = FindViewById<Button>(Resource.Id.btnSave);
TextView txtShow = FindViewById<TextView>(Resource.Id.txtSoru);
EditText txtEnter = FindViewById<EditText>(Resource.Id.txtSoruGir);
Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();
SQLitePCL.Batteries.Init();
// IMobileServiceTable<MainActivity> SurveyTable = client.GetTable<MainActivity>();
// Initialization for Azure Mobile Apps
Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();
// This MobileServiceClient has been configured to communicate with the Azure Mobile App and
// Azure Gateway using the application url. You're all set to start working with your Mobile App!
Microsoft.WindowsAzure.MobileServices.MobileServiceClient TrendDemoClient = new Microsoft.WindowsAzure.MobileServices.MobileServiceClient(
"http://******.azurewebsites.net");
// Set our view from the "main" layout resource
// SetContentView (Resource.Layout.Main);
}
);
[Java.Interop.Export()]
public async void AddItem(View view)
{
var client = new MobileServiceClient("http://xx.azurewebsites.net/");
// IMobileServiceTable SurveyTable = client.GetTable("Survey");
var table = client.GetSyncTable<MyFirstDbTableModel>();
var myFirstModelInstance = new MyFirstDbTableModel();
myFirstModelInstance.Id = Guid.NewGuid().ToString();
myFirstModelInstance.Questions = "Could I help you with this answer?";
myFirstModelInstance.Complete = false;
try
{
await table.InsertAsync(myFirstModelInstance);
}
catch (Exception e)
{
CreateAndShowDialog(e, "Error");
}
}
private void CreateAndShowDialog(Exception exception, String title)
{
CreateAndShowDialog(exception.Message, title);
}
private void CreateAndShowDialog(string message, string title)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetMessage(message);
builder.SetTitle(title);
builder.Create().Show();
}
}
}
MyFirstDbTableModel Class
using Newtonsoft.Json;
namespace App4
{
public class MyFirstDbTableModel
{
// string id;
public string Id { get; set; }
public string Questions { get; set; }
public bool Complete { get; set; }
}
}
The Error Message has changed.
Error
-Synccontext is not yet initialized
Output (when I press the Button )
06-28 14:20:48.971 D/ViewRootImpl#16dc384[MainActivity]( 4390): ViewPostImeInputStage processPointer 0
06-28 14:20:49.025 D/ViewRootImpl#16dc384[MainActivity]( 4390): ViewPostImeInputStage processPointer 1
06-28 14:20:49.075 D/ViewRootImpl#5067e47[MainActivity]( 4390): ThreadedRenderer.create() translucent=true
06-28 14:20:49.079 D/InputTransport( 4390): Input channel constructed: fd=74
06-28 14:20:49.079 D/ViewRootImpl#5067e47[MainActivity]( 4390): setView = DecorView#279674[MainActivity] touchMode=true
06-28 14:20:49.081 D/ViewRootImpl#5067e47[MainActivity]( 4390): dispatchAttachedToWindow
06-28 14:20:49.093 D/ViewRootImpl#5067e47[MainActivity]( 4390): Relayout returned: oldFrame=[0,0][0,0] newFrame=[36,1062][1404,1593] result=0x27 surface={isValid=true 504091385344} surfaceGenerationChanged=true
06-28 14:20:49.094 D/ViewRootImpl#5067e47[MainActivity]( 4390): mHardwareRenderer.initialize() mSurface={isValid=true 504091385344} hwInitialized=true
06-28 14:20:49.099 D/mali_winsys( 4390): EGLint new_window_surface(egl_winsys_display*, void*, EGLSurface, EGLConfig, egl_winsys_surface**, egl_color_buffer_format*, EGLBoolean) returns 0x3000, [1624x787]-format:1
06-28 14:20:49.099 D/ScrollView( 4390): onsize change changed
06-28 14:20:49.110 D/ViewRootImpl#5067e47[MainActivity]( 4390): MSG_WINDOW_FOCUS_CHANGED 1
06-28 14:20:49.110 D/ViewRootImpl#5067e47[MainActivity]( 4390): mHardwareRenderer.initializeIfNeeded()#2 mSurface={isValid=true 504091385344}
06-28 14:20:49.124 D/ViewRootImpl#5067e47[MainActivity]( 4390): MSG_RESIZED_REPORT: ci=Rect(0, 0 - 0, 0) vi=Rect(0, 0 - 0, 0) or=1
06-28 14:20:49.141 D/ViewRootImpl#16dc384[MainActivity]( 4390): MSG_WINDOW_FOCUS_CHANGED 0
06-28 14:20:49.142 D/SEM_CLIP_SemClipboardManager( 4390): isCocktailBarDisplayed : false
You use your MainActivity as database model. You shouldn't do that.
What you should do:
Create a database table model:
public class MyFirstDbTableModel
{
// string id;
public string Id { get; set; }
public string Questions { get; set; }
public bool Complete { get; set; }
}
Get the table like this:
var table = client.GetTable<MyFirstDbTableModel>();
Create an instance of your database table model and add values:
var myFirstModelInstance = new MyFirstDbTableModel();
myFirstModelInstance.Id = Guid.NewGuid().ToString();
myFirstModelInstance.Questions = "Could I help you with this answer?";
myFirstModelInstance.Complete = false;
Put the instance to the database in Azure:
var inserted = await table.InsertAsync(myFirstModelInstance);
Notice: It is necessary, that you have created the database table from the backend or directly in the database. You can use the "MyFirstDbTableModel" model directly in the backend, but it must inherit from
public class MyFirstDbTableModel : EntityData {...}
and you have to do some more stuff, like to create a controller, which is described in the Azure documentation or you can see it in the Azure Mobile Apps Quickstart example.
Update based on the edit of the question:
You should change the class name from "Controller" to "MyFirstDbTableModelController". The name of the controller is reflected in the request link.
You should connect to your database with SSMS (SQL Server Management Studio). With this program you can see, whether the table is already created. Note: The table will only created, after you has send a request to the backend.
You don't use attributes in your model, with this, I have removed the attributes in step 3.
Have you changed the Initializer in the Startup.MobileApp.cs?
If you use the code in my answer, it should work with the changes from my update.
If you getting an exception, please post the stacktrace. Additionally you can debug the backend, as well, to get an stacktrace.
Notice: If you change the model, for example the MyFirstDbTableModel, the database will not create the table again. You have to drop all tables in the db.
This part is wrong:
[JsonProperty(PropertyName = "id")]
public string Id
{
get { return Id; }
set { Id = value; }
}
You are returning Id which in turns return Id, so it is a recursive call. It can't work.
I also agree with Sean that this isn't the best practice to add the data to Azure.
I must be missing something here. Every example I've seen with Android's 2-way binding is based on a String in the backing data for anything user-enterable, like an EditText.
Handling anything not a String seems somewhat... inelegant. For example, if I have a double in my domain model that needs to be editable, the best binding I've come up with requires a ViewModel with surprisingly a lot of code to interface between the model and the EditText.
Am I missing something key? Should I really need 30 lines of code to interface an EditText with a double? For the sake of discussion, let's consider a currency field, represented as a double, in a two-way bound EditText:
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:text="#={fragModel.startBucks}"
android:id="#+id/editText2"/>
And here's the ViewModel I've had to construct in order to give the EditText a string to bind to.
#Bindable private String startBucksString;
private double localBucks;
public String getStartBucksString() {
double domainBucks = cd.getStartBucks();
// Ignore incoming change of less than rounding error
if( Math.abs(localBucks - domainBucks) < .001 ) return startBucksString;
startBucksString = "";
if( domainBucks != 0)
startBucksString = String.format("$%.2f", domainBucks);
return startBucksString;
}
public void setStartBucksString(String inBuckstr) {
double calcBucks=0;
inBuckstr = inBuckstr.replaceAll( "[^\\d.]", "" );
try {
calcBucks = Double.parseDouble(inBuckstr);
} catch( NumberFormatException e) {
return;
}
// Neglect outgoing change of less than rounding error
if( Math.abs(localBucks - calcBucks) < .001 ) return;
startBucksString = String.format("$%.2f", calcBucks);
localBucks = calcBucks;
cd.setStartBucks(calcBucks);
notifyPropertyChanged(BR.startBucksString);
}
Here, I wrote a simple, compilable example of 2-way binding with a ViewModel. It illustrates the difficulty I had in continuously updating a float in the domain model -- in the end, I decided there's no way to do it without writing a custom TextWatcher for each domain field.
My approach is to delay notifyPropertyChanged method calling using Handles. In this way while the user is typing, the code don't run, then 2,5 seconds after the user has stopped to type last character, the notificationPropertyChanged will be called.
The visual effect is cool, and the user is free to write numbers as he wants.
See these two examples:
Use can use this compact(?) code for each field:
//
// g1FuelCostYear field
//
private double g1FuelCostYear;
#Bindable
public String getG1FuelCostYear() {
return Double.valueOf(g1FuelCostYear).toString();
}
private Handler hG1FuelCostYearDelay = null;
public void setG1FuelCostYear(String g1FuelCostYear) {
// Delayed notification hadler creation
if (hG1FuelCostYearDelay == null) {
hG1FuelCostYearDelay = new Handler() {
#Override
public void handleMessage(Message msg) {
notifyPropertyChanged(it.techgest.airetcc2.BR.g1FuelCostYear);
}
};
} else {
// For each call remove pending notifications
hG1FuelCostYearDelay.removeCallbacksAndMessages(null);
}
// Data conversion logic
try {
this.g1FuelCostYear = Double.parseDouble(g1FuelCostYear);
}
catch (Exception ex) {
this.g1FuelCostYear = 0.0;
log(ex);
}
// New delayed field notification (other old notifications are removed before)
hG1FuelCostYearDelay.sendEmptyMessageDelayed(0,2500);
}
This code instead is useful when you use currency converter or percent converter. The user can write a plain double, the code convert to currency string. If the setter is called with the currency string the code is able to convert it as double too.
//
// g1FuelCostYear field
//
private double g1FuelCostYear;
#Bindable
public String getG1FuelCostYear() {
NumberFormat nf = NumberFormat.getCurrencyInstance();
return nf.format(this.g1FuelCostYear);
//return Double.valueOf(g1FuelCostYear).toString();
}
private Handler hG1FuelCostYearDelay = null;
public void setG1FuelCostYear(String g1FuelCostYear) {
if (hG1FuelCostYearDelay == null)
{
hG1FuelCostYearDelay = new Handler() {
#Override
public void handleMessage(Message msg) {
notifyPropertyChanged(it.techgest.airetcc2.BR.g1FuelCostYear);
}
};
} else {
hG1FuelCostYearDelay.removeCallbacksAndMessages(null);
}
boolean success = false;
try {
NumberFormat nf = NumberFormat.getCurrencyInstance();
this.g1FuelCostYear = nf.parse(g1FuelCostYear).doubleValue();
success = true;
}
catch (Exception ex) {
this.g1FuelCostYear = 0.0;
log(ex);
}
if (!success) {
try {
this.g1FuelCostYear = Double.parseDouble(g1FuelCostYear);
success = true;
} catch (Exception ex) {
this.g1FuelCostYear = 0.0;
log(ex);
}
}
updateG1FuelConsumption();
hG1FuelCostYearDelay.sendEmptyMessageDelayed(0,2500);
}
I have an app setup and I have been testing it with Android pretty successfully but now I am trying to add in iOS and it doesn't seem to work at all.
I have a MasterDetailPage that I mostly copied from the mobileCRM project which works fine for me on Adroid and my buildhost but not in my ported over version.
public class RootPage : MasterDetailPage
{
OptionItem _previousItem;
public RootPage()
{
var optionsPage = new MenuPage { Icon = "Icon.png", Title = "menu" };
optionsPage.Menu.ItemSelected += (sender, e) => NavigateTo(e.SelectedItem as OptionItem);
Master = optionsPage;
NavigateTo(optionsPage.Menu.ItemsSource.Cast<OptionItem>().First());
}
void NavigateTo(OptionItem option)
{
if (_previousItem != null)
_previousItem.Selected = false;
option.Selected = true;
_previousItem = option;
var displayPage = PageForOption(option);
Detail = new NavigationPage(displayPage)
{
BarBackgroundColor = Helpers.Color.Orange.ToFormsColor()
};
Detail.SetValue(TitleProperty, option.Title);
Detail.SetValue(IconProperty, option.Icon);
IsPresented = false;
}
static Page PageForOption(OptionItem option)
{
switch (option.Title)
{
case "Home":
return new HomePage();
case "Circles":
return new CirclesPage();
case "Settings":
return new SettingsPage ();
default:
throw new NotImplementedException("Unknown menu option: " + option.Title);
}
}
}
This looks like a bug in Xamarin.Forms, and I've reported it.
I was able to reproduce your issue, but also able to workaround it by replacing the ListView in OptionPage by a StackLayout of Buttons :
public MenuPage (MasterDetailPage mdpage)
{
Content = new StackLayout {
Children = {
new Button { Text = "Home", Command = new Command (() => {mdpage.Detail = new HomePage ();mdpage.IsPresented = false;}) },
new Button { Text = "Circles", Command = new Command (() => {mdpage.Detail = new CirclesPage ();mdpage.IsPresented = false;}) },
new Button { Text = "Settings", Command = new Command (() => {mdpage.Detail = new SettingsPage ();mdpage.IsPresented = false;}) },
}
};
}
In one of my projects, I'm using a TableView in the Master, but just like the ListView here, it triggers the issue.
I'm making a very simple software with a TCP Listener that (until now) only receives a message from a TCP Client codified in ASCII and I'll have to do something with the UI that I still don't know, but by now, I'm just trying to show an AlertDialog with this message on Samsung Galaxy Tab.
The problem is that, I believe that for some reason the setContentView is not working. I have one .axml (layout) file with an AbsoluteLayout, and I'm calling this AbsoluteLayout on code, changing its color, and trying to show this AbsoluteLayout (with its color changed) on the screen, but the problem is that I just see the regular black screen.
I started debugging the code, and I can see the all the Console.Writeline commands on the output of the MS VS 2010, even the message sent from the client. But I can't see the layout and the AlertDialog.
Can anybody help me? Thanks in advance.
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Android.Graphics.Drawables;
using System.Drawing;
namespace Gafisa.Automacao.VideoWall.Listener
{
[Activity(Label = "Listener", MainLauncher = true, Icon = "#drawable/icon")]
public class Activity1 : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
AbsoluteLayout abs = FindViewById<AbsoluteLayout>(Resource.Id.abslayout);
abs.SetBackgroundColor(new Android.Graphics.Color(125,125,125,125));
//ImageButton btn = new ImageButton(this);
//var lp = new AbsoluteLayout.LayoutParams(50, 50, 200, 200);
//btn.LayoutParameters = lp;
//BitmapDrawable dd = new BitmapDrawable("/mnt/sdcard/1.png");
//btn.SetBackgroundDrawable(dd);
//abs.AddView(btn);
System.Net.Sockets.TcpListener listener = null;
byte[] rcvBuffer = new byte[40];
int bytesRcvd;
try
{
listener = new System.Net.Sockets.TcpListener(IPAddress.Any, 13000);
listener.Start();
Console.WriteLine("Listener iniciado");
}
catch (SocketException se)
{
Console.WriteLine("Erro ao iniciar o listener: " + se.Message);
}
for (;;)
{
TcpClient client = null;
NetworkStream netStream = null;
try
{
client = listener.AcceptTcpClient();
netStream = client.GetStream();
int totalBytesEchoed = 0;
while ((bytesRcvd = netStream.Read(rcvBuffer, 0, rcvBuffer.Length)) > 0)
{
netStream.Write(rcvBuffer, 0, bytesRcvd);
totalBytesEchoed += bytesRcvd;
}
string recebido = System.Text.Encoding.ASCII.GetString(rcvBuffer);
Console.WriteLine(recebido);
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.SetMessage(recebido);
alert.SetTitle("Mensagem Recebida");
alert.Show();
Console.WriteLine("echoed {0} bytes.", totalBytesEchoed);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine("Erro no LOOP");
}
finally
{
netStream.Close();
client.Close();
}
}
}
}
}
By running an infinite loop in this OnCreate function, you prevents the UI framework from finishing rendering. That's why what you see is only the black screen.
You are supposed to run non-UI code asynchronously (in a separate thread).
Change it to
Task.Factory.StartNew(() =>
{
for (;;)
{
TcpClient client = null;
NetworkStream netStream = null;
try
{
client = listener.AcceptTcpClient();
netStream = client.GetStream();
int totalBytesEchoed = 0;
while ((bytesRcvd = netStream.Read(rcvBuffer, 0, rcvBuffer.Length)) > 0)
{
netStream.Write(rcvBuffer, 0, bytesRcvd);
totalBytesEchoed += bytesRcvd;
}
string recebido = System.Text.Encoding.ASCII.GetString(rcvBuffer);
Console.WriteLine(recebido);
RunOnUiThread(() =>
{
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.SetMessage(recebido);
alert.SetTitle("Mensagem Recebida");
alert.Show();
}
Console.WriteLine("echoed {0} bytes.", totalBytesEchoed);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine("Erro no LOOP");
}
finally
{
netStream.Close();
client.Close();
}
}
}
Also with Mono for Android (Xamarin.Android) you should use Log.Info(string tag, string message) where tag is the name of calling class. Don't use Console.WriteLine(string). You can also use Log.Warn(string, string) and Log.Error(string, string)