ICommand not always firing when tab selected - xamarin.android

I have a simple ActionBar with 3 tabs attached. When a tab is clicked, the fragment is inflated and the view shows. The tab being click event is fired using an event. Initially, the first fragment is inflated, but the others respond and inflate if clicked.
If I change the event being fired to an ICommand, only the last fragment is inflated and then if I click on the first tab, that and the last are inflated. Never the second.
My code is this
ICommand TabClicked
{
get
{
return new RelayCommand(() =>
{
tab.TabSelected += (object sender, ActionBar.TabEventArgs e) => TabOnTabSelected(sender, e);
});
}
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
fragments.Add(new TODFragment());
fragments.Add(new ConditionsFragment());
fragments.Add(new ResultsFragment());
AddTabToActionBar("Time", Resource.Drawable.crucifix_colour);
AddTabToActionBar("Conditions", Resource.Drawable.weather_colour);
AddTabToActionBar("Results", Resource.Drawable.tod_colour);
}
void AddTabToActionBar(string text, int iconResourceId)
{
tab = ActionBar.NewTab().SetTag(text).SetText(text).SetIcon(iconResourceId);
/* uncomment and comment out one of the two below to see the difference in operation */
tab.TabSelected += TabOnTabSelected;
//tab.SetCommand<ActionBar.TabEventArgs>("TabSelected", TabClicked);
ActionBar.AddTab(tab);
}
void TabOnTabSelected(object sender, ActionBar.TabEventArgs tabEventArgs)
{
var tabNo = sender as ActionBar.Tab;
var frag = fragments[tabNo.Position];
tabEventArgs.FragmentTransaction.Replace(Resource.Id.frameLayout1, frag);
}
Am I missing something fundamental here in the difference between ICommands and Events or is it something else?
I'm using Xam.Android and MVVMLight

I found the answer. When I create the partial class I define the UI objects like this (or something like this at least)
EditText myEditText;
EditText MyEditText = myEditText ?? (view.FindViewById<EditText>(Resources.Id.myEdit);
This is fine, but it does mean that once defined, it doesn't get redefined.
Not a problem if the UI is not really going to change, but every time an action tab is pressed, the fragment is refreshed. Only problem is the Id isn't changing as myEditText is not null.
The answer is add a method in the UI definition code that nulls the objects then in the main code, when the UI disappears, call the nulling method. Everything works then

Related

Memory leaks with NavigationController and multiple UIViewController

In my application I manage 4 UIViewController :
First one for handle user login
Second one that display the menu
Third one for displaying the action available depending on the user entry in previous view.
Fourth one that show details infos about previous selected item on third view.
Third and Four are displaying a UIViewTable with item to select that are passed to the next UIViewController to display info.
So I made attention to not using hard link to each other. Parameters are passed when UIViewControlle are instantiate.
Here is the probleme that I'm dealing with :
When I pass from Third to Fourth, and then go back to Fourth, memory is never been released. Worth, when I return to Fourth it take the same amount of memory again.
After 3-4 round-trip the device memory is full. (Another strange behavior here with UIImageView that take 12Mo of memory any picture, no mater the picture size is 12Ko or 120Ko...)
Here is the code that is use to switch between controllers :
//First UIViewController, tagged as Root in storyboard
public partial class LoginController
{
//....some logic code here
//User ask for login by pressed a button , after an echange with a server
//for credential verification, if the server reply "ok" then I call the second
//UIViewController like this
void PushHomeScreen()
{
HomeController homeController = Storyboard.InstantiateViewController("home") as HomeController;
NavigationController.PushViewController(homeController, false);
}
}
public partial class HomeController
{
//....some logic code here
//The view displaying 3 UIButton that root to 2 different UIViewController.
//First one is deadend, and user can only go back. It will be used one time to select a dataset to download and use after.
//The two next use the same UIViewController, only the way that the controller handle the nexts user action is different :
//If user came from the Second then it display screen for retrieving user attendance
//If user came from the Third then it display screen for retrievin user survey.
//So here are the code behind:
private void BtnDownload_TouchUpInside(object sender, EventArgs e)
{
DownloadController downloadTrainingController = Storyboard.InstantiateViewController("downloadTraining") as DownloadController;
NavigationController.PushViewController(downloadTrainingController, false);
}
private void BtnSurvey_TouchUpInside(object sender, EventArgs e)
{
TrainingAndReviewListController trakningController = Storyboard.InstantiateViewController("trakning") as TrainingAndReviewListController;
trakningController.backgroundColor = "violet";
NavigationController.PushViewController(trakningController, false);
}
private void BtnTraining_TouchUpInside(object sender, EventArgs e)
{
TrainingAndReviewListController trakningController = Storyboard.InstantiateViewController("trakning") as TrainingAndReviewListController;
trakningController.backgroundColor = "red";
NavigationController.PushViewController(trakningController, false);
}
}
//I will not show you the DownloadController as it is not pertinent to the current probleme.
public partial class TrainingAndReviewListController
{
//To handle the back action, so user go from this to HomeController
void BtnHome_TouchUpInside(object sender, EventArgs e)
{
NavigationController.PopViewController(false);
}
private void Tablesource_OnRowSelected(object sender, DownloadTableSource.RowSelectedEventArgs e)
{
serviceController = Storyboard.InstantiateViewController("service") as ServiceController;
serviceController.backgroundColor = backgroundColor;
serviceController.training = e.training;
NavigationController.PushViewController(serviceController, false);
}
}
//Next are the final UIViewController on the storyboard. It consume a lot of memory in inspector due to 20 UIViewImage on it who each consume 12Mo of memory.
//When I come back from this Controller to the previous memory are never released, and if I return on it, it consume the same amount extra again
//One solution to prevent this is to instanciate him once in TrainingAndReviewListController as class attribute, and then reuse it. But it is not supposed to be the role
//of the NavigationController?
public partial class ServiceController
{
public string backgroundColor = "red";
public Training training;
//To handle the user action for going to the Home controller, this is not the action that cause memory leaks, altoutgh it will cause the same behavior I think.
void BtnHome_TouchUpInside(object sender, EventArgs e)
{
foreach (UIViewController uiviewcontroller in NavigationController.ViewControllers) {
if (uiviewcontroller.GetType() == typeof(HomeController)) {
NavigationController.PopToViewController(uiviewcontroller as HomeController, false);
}
}
}
void BtnGoBack_TouchUpInside(object sender, EventArgs e)
{
Console.WriteLine(NavigationController.ViewControllers.Count()); //It always log the same count, as if the controller is really be poped when i come back and pop again.
//But profilage show me that memory still used by this instance.
NavigationController?.PopViewController(false);
}
}

ListGrid put focus in the FilterEditor

I have a ListGrid defined like this:
ListGrid lgrid = new ListGrid();
ListGridField first = new ListGridField("first",first");
ListGridField second = new ListGridField("second ",second ");
lgrid.setFields(first, second);
lgrid.setShowFilterEditor(true);
¿How can i put the keyboard focus in the first filter editor field after i call show() in the layout?
Thxs in advance.
Depending on what your use case is (which would be useful to provide a more focused answer), the solution you posted might not be what you really need, because if you scroll on your ListGrid, it could trigger a new data fetch (if there are more records to show), and move the cursor to the filter editor as a result (if your user is editing some records at that point, the cursor moving to the filter row is not what she would want to happen!!).
In such a case, you probably just want to call grid.focusInFilterEditor("fieldToFocus") after the listGrid.show() statement or in the ClickHandler of some button you use to fetch the data, etc.
Anyway, you don't need the Timer either. This works:
listGrid.addDataArrivedHandler(new DataArrivedHandler() {
#Override
public void onDataArrived(DataArrivedEvent event) {
grid.focusInFilterEditor("fieldToFocus");
}
});
I got the solution, its focusInFilterEditor, this is an example to set the focus after the data arrived to the grid:
// Put the focus on the first listGrid field when is loaded
listGrid.addDataArrivedHandler(new DataArrivedHandler() {
#Override
public void onDataArrived(DataArrivedEvent event) {
Timer t = new Timer() {
public void run() {
if(listGrid.getFilterEditorCriteria() == null){
listGrid.focusInFilterEditor("fieldToFocus");
}
}
};
t.schedule(600);
}
});

How to open custom dialog box / popup using Xamarin.Forms?

I am newbie to Xamarin.Forms and stuck with a situation where I want to open up a popup box with my control details [e.g. View Employee Details] on click of parent page.
How can I open custom dialog box / popup using Xamarin.Forms?
Any example code will be appreciated?
Thanks in advance!
If you still want to have your popup's code in its own Page you can set up some custom renderers along the following logic.
1. A ModalPage & corresponding renderer
public class ModalPage : ContentPage { }
public class ModalPageRenderer : PageRenderer {
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
this.View.BackgroundColor = UIColor.Clear;
this.ModalPresentationStyle = UIModalPresentationStyle.OverCurrentContext;
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
SetElementSize (new Size (View.Bounds.Width, View.Bounds.Height));
}
}
2. HostPage
public class ModalHostPage : ContentPage, IModalHost
{
#region IModalHost implementation
public Task DisplayPageModal(Page page)
{
var displayEvent = DisplayPageModalRequested;
Task completion = null;
if (displayEvent != null)
{
var eventArgs = new DisplayPageModalRequestedEventArgs(page);
displayEvent(this, eventArgs);
completion = eventArgs.DisplayingPageTask;
}
// If there is no task, just create a new completed one
return completion ?? Task.FromResult<object>(null);
}
#endregion
public event EventHandler<DisplayPageModalRequestedEventArgs> DisplayPageModalRequested;
public sealed class DisplayPageModalRequestedEventArgs : EventArgs
{
public Task DisplayingPageTask { get; set;}
public Page PageToDisplay { get; }
public DisplayPageModalRequestedEventArgs(Page modalPage)
{
PageToDisplay = modalPage;
}
}
}
3. HostPage renderer
public class ModalHostPageRenderer: PageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if(e.OldElement as ModalHostPage != null)
{
var hostPage = (ModalHostPage)e.OldElement;
hostPage.DisplayPageModalRequested -= OnDisplayPageModalRequested;
}
if (e.NewElement as ModalHostPage != null)
{
var hostPage = (ModalHostPage)e.NewElement;
hostPage.DisplayPageModalRequested += OnDisplayPageModalRequested;
}
}
void OnDisplayPageModalRequested(object sender, ModalHostPage.DisplayPageModalRequestedEventArgs e)
{
e.PageToDisplay.Parent = this.Element;
var renderer = RendererFactory.GetRenderer (e.PageToDisplay);
e.DisplayingPageTask = this.PresentViewControllerAsync(renderer.ViewController, true);
}
}
Then it is as simple as calling
await ModalHost.DisplayPageModal(new PopUpPage());
from your host page or in this particular case from the ViewModel behind.
What Pete said about PushModalAsync / PopModalAsync still remains valid for this solution too (which in my opinion is not a disadvantage), but your popup would appear with transparent background.
The main advantage of this approach, in my opinion, is that you can have your popup XAML/code definition separate from the host page and reuse it on any other page where you wish to show that popup.
The general purpose of what you are trying to achieve can be accomplished by using the PushModalAsync and PopModalAsync methods of Xamarin.Forms Navigation object.
The chances are that this is good enough for what you are needing - However - this isn't truely modal. I will explain after a small code snippet:-
StackLayout objStackLayout = new StackLayout()
{
};
//
Button cmdButton_LaunchModalPage = new Button();
cmdButton_LaunchModalPage.Text = "Launch Modal Window";
objStackLayout.Children.Add(cmdButton_LaunchModalPage);
//
cmdButton_LaunchModalPage.Clicked += (async (o2, e2) =>
{
ContentPage objModalPage = new ContentPage();
objModalPage.Content = await CreatePageContent_Page2();
//
await Navigation.PushModalAsync(objModalPage);
//
// Code will get executed immediately here before the page is dismissed above.
});
//
return objStackLayout;
private async Task<StackLayout> CreatePageContent_Page2()
{
StackLayout objStackLayout = new StackLayout()
{
};
//
Button cmdButton_CloseModalPage = new Button();
cmdButton_CloseModalPage.Text = "Close";
objStackLayout.Children.Add(cmdButton_CloseModalPage);
//
cmdButton_CloseModalPage.Clicked += ((o2, e2) =>
{
this.Navigation.PopModalAsync();
});
//
return objStackLayout;
}
The problem with the above is that the
await Navigation.PushModalAsync(objModalPage);
will immediately return after the animation.
Although you can't interact with the previous page, as we are displaying a new NavigationPage with a Close button shown - the parent Navigation Page is still executing behind the scenes in parallel.
So if you had any timers or anything executing these still would get called unless you stopped those.
You could also use the TaskCompletionSource approach as outlined in the following post also How can I await modal form dismissal using Xamarin.Forms?.
Note - that although you can now await the 2nd page displaying and then when that page is dismissed allowing code execution to continue on the next line - this is still not truely a modal form. Again timers or anything executing still will get called on the parent page.
Update 1:-
To have the content appear over the top of existing content then simply include it on the current page, however make this section of content invisible until you need it.
If you use an outer container such like a Grid that supports multiple child controls in the same cell, then you will be able to achieve what you want.
You will also want to use something like a filled Box with transparency that will cover the entire page also, to control the visible, see through section, that surrounds your inner content section.
I followed above approach and found it impossible to run on iOS 7.
I found this library BTProgressHUD which you can modify and use.
I Use its methods by Dependency service.
Actual library for popups.
https://github.com/nicwise/BTProgressHUD
Following example uses BTProgressHUD library internally.
https://github.com/xximjasonxx/ScorePredictForms

StateHelper strange behavior difference for put and add

I'm currently implementing a backing component for a composite component. I'm using the state helper to keep the internal state during requests and I found a strange behavior I can't explain to myself.
The composite component has a button which adds a new item to an ArrayList. This list was created by myself and added to the state. This is the code:
public void buttonActionListener() {
List<Item> itemList = getItemList();
if(itemList == null) {
itemList = new ArrayList<>();
setItemList(itemList);
}
itemList.add(item);
}
public List<Item> getItemList() {
return (List<Item>) getStateHelper().get(PropertyKeys.itemList);
}
private void setItemList(List<Item> itemLis) {
getStateHelper().put(PropertyKeys.itemList, itemList);
}
The list is the displayed in the composite component with a dataTable. After the first request/button click I have the list with one item in it. The second click will show me two items in the datatable, but it seems, that nothing is stored to the state helper. Because the third click only displays the items #1 and #3 but #2 is lost. Each succeeding click will always only show me item #1 and #n.
But when I use
public void buttonActionListener() {
addItemList(item);
}
public List<Item> getItemList() {
return (List<Item>) getStateHelper().get(PropertyKeys.itemList);
}
private void addItemList(Item item) {
getStateHelper().add(PropertyKeys.itemList, item); // add instead of put
}
everything works as desired. Both methods (implementations) in the StateHelper do nearly the same. Please could you explain me what's going on?
I had a similar behavior in the past while I was testing around. But at this time I didn't use a list but just the Item which should be changed in the state saving. I always got the first added state, never the changed one.
I'm using Mojarra 2.1.28 on a JBoss 7.1.3.

After clicking a button, how do I wait for SwingWorker to finish before proceeding?

I have the following code:
public class FileLoader extends SwingWorker(Void, Void) {
#Override
private Void doInBackground() {
loadFiles();
}
}
public class LogInPage {
private FileLoader fileLoader = new FileLoader();
public LogInPage() {
fileLoader.execute();
}
loginButtonActionPerformed(ActionEvent evt) {
//wait for files to finish loading
//while displaying a waiting cursor
showMainForm();
}
}
My question would be:
After clicking the button, I would want all the files to be loaded first (while displaying an hourglass cursor and progress bar) before showing the main form.
I have done this before with Thread's join() but was not able to do the same with SwingWorker.
I have read about overriding done() and implementing listeners but I can't apply it here.
Any help?
Thanks.
From what you're saying and contrary to what you think, I think you can actually use SwingWorker's done() method. Before execute(), disable the button, start a busy animation, whatever, then in the done() method, do whatever it is you need to do to continue the program. That's what it's for :-)
You should also look at the SwingWorker.publish() and process() to send and receive the progress bar events.
See also: How do I wait for a SwingWorker's doInBackground() method?

Resources