Xamarin Forms add new controls dinamically in the content via code - xamarin.forms

I have a form in my Xamarin project that its result is something like that:
Basically there is a header (1) and a body of this form (2). The header is quite simple build with AbsoluteLayout.
For creating the body (2) I've created my component to show a tab control and then for each tab a specific grid with images and text. For each section, I'm checking in the database how many records there are for it and change the text. This activity is very long and I'm trying to understand why and how I can improve speed.
Then I should cut the corner to add later in my page the tab control so the user can see immediately the header and after few second all page. The code is like the following:
public class MyPage : WaitingPage
{
public MyPage(Card card)
{
LoadingMessage = "Loading...";
ShowLoadingFrame = true;
ShowLoadingMessage = true;
ShadeBackground = true;
WaitingOrientation = StackOrientation.Vertical;
IsWaiting = true;
StackLayout stackPage = new StackLayout() {
BackgroundColor = Color.FromHex("#fff"),
Children = {
ShowHeader(card),
}
};
Content = stackPage;
Task.Yield();
Task.Run(async () => {
Content = await ShowDetails(card);
});
IsWaiting = false;
}
}
I tried different ways to add the content from await ShowDetails(card); but nothing happens. Basically the content doesn't change unless await ShowDetails(card); is executed. I discovered Task.Yield(); (you can wait the page is rendered and showed and then continue) but in this case doesn't work. And also WaitingPage doesn't show the message.
Any suggestions or help? Thank you in advance

Related

Removing Tabbar from tabbedpage leaves blank space. How to remove it?

I want to remove Tabbar from TabbedPage. I got it working but it leaves blank space or page height is not updated after hiding Tabbar.
Note that when we swipe through the pages, the blank goes and never comes back. This issue appears only for the first time.
I have tried from this link. But it doesn't work.
Also tried following
private void Element_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
try
{
TabBar.Hidden = true;
//TabBar.Bounds = new CoreGraphics.CGRect(View.Subviews[0].Frame.X, View.Subviews[0].Frame.Y,
// View.Subviews[0].Frame.Width, 0);
if (TabBar.Hidden)
{
// page
View.Subviews[0].Frame = new CoreGraphics.CGRect(0, 0, View.Subviews[1].Frame.Width, NativeView.Frame.Height);
// Tabbar
View.Subviews[1].Frame = new CoreGraphics.CGRect(View.Subviews[0].Frame.X, View.Subviews[0].Frame.Y,
View.Subviews[0].Frame.Width, 0);
}
else
{
View.Subviews[1].Frame = new CoreGraphics.CGRect(View.Subviews[1].Frame.X, View.Subviews[1].Frame.Y,
View.Subviews[1].Frame.Width, 49);
View.Subviews[0].Frame = new CoreGraphics.CGRect(View.Subviews[0].Frame.X, View.Subviews[0].Frame.Y,
View.Subviews[0].Frame.Width, View.Subviews[0].Frame.Height - 49);
}
//if (TabBar.Hidden)
// View.Subviews[1].Frame = new CoreGraphics.CGRect(View.Subviews[1].Frame.X, View.Subviews[1].Frame.Y, View.Subviews[1].Frame.Width, 0);
//else
// View.Subviews[1].Frame = new CoreGraphics.CGRect(View.Subviews[1].Frame.X, View.Subviews[1].Frame.Y, View.Subviews[1].Frame.Width, 49);
}
catch (Exception ex)
{
//TraceLog("Element_PropertyChanged" + ex.Message);
}
}
EDIT
I open tabbed page on list view item clicked. I allow to add multiple tabs dynamically. Also I have created custom tabbar using ContentView that I update when pages are added or removed from TabbedPage.
The sequence is :
- User opens first tab.
- Click on home icon given on tabbed page.
- open the second page by click on another list item
- swipe through the page and the page height will be normal.
Here is the code on ListView_ItemTapped (Not posted exactly how is it actually but you can get idea :) )
MultiTab ObjMultiTab = new MultiTab(); // Initialize tabbed page
// Get data from server
ObjMultiTab.Data = ObjData;
int Id = Convert.ToInt32(ObjData.id);
if (ActiveList.ContainsKey(Id)) // Dictionary that contains info about index and pageid that are already open
{
TabId = ActiveList[ObjData.id];
CurrentPage = Children[TabId]; // If user taps on already opend page
}
else
{
Count += 1; ActiveList.Add(Id, Count);
Children.Add(new SecondTabbedPage(TableData)); // Or add new child
CurrentPage = Children[Count];
}
await Application.Current.MainPage.Navigation.PushModalAsync(ObjMultiTab);
Is there any way to remove blank space for the first time also ?
Adding following function to the renderer removed blank space from TabbedPage.
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
TabBar.Hidden = true;
var page = View.Subviews[0];
var tabbar = View.Subviews[1];
tabbar.Bounds = CGRect.Empty;
page.Bounds = UIScreen.MainScreen.Bounds;
}
There is a solution that doesn't require any renders and works on both Android and iOS.
Wrap the TabbedPage in a NavigationPage so the structure of your app becomes
NavigationPage (root)
TappedPage
NavigationPage
ContentPage (with tabbar)
ContentPage (without tabbar)
On the TabbedPage you have to hide the navigationbar of the 'root' NavigationPage, otherwise you have 2 navbars.
<TabbedPage
...
HasNavigationBar="False">
If you push a page using the 'root' NavigationPage, the tabbar is hidden and there is no blank space at the bottom.
--- Edit ---
See my example at:
https://github.com/Jfcobuss/HideTabbarExample/tree/master/HideTabbarExample
Downside of this solution is
It is bit of an hacky workaround
The title next to back-button is the title of the TabbedPage, not the current tab
The animation to the next page is not as fluent as default

Xamarin Forms - Duplicate Screens

I'm writing an app using Xamarin Forms and I have an issue I was hoping someone can help with.
My app contains a screen which has multiple icons that can be pressed which would then open a new screen.
My issue is that if you press the icon twice really fast, the app opens up 2 instances of the same screen (it's not just related to a double press, if you press the icon 6 times very fast it will open up 6 duplicate screens). Pressing the Back button, closes the top screen to reveal the duplicate screen underneath. Pressing the Back button again navigates you back to the original screen.
This issue seems to occur on any screen within my app so I'm hoping other people will have experienced it and know of a solution to prevent duplicate screens being displayed.
This is a known issue in TapEvents.
My hack is, in the code-behind, have a bool variable _canTap.
Inside the method you are calling to push new page, first you check if canTap, then set to false, and only set to true after navigating to another page. This way all taps will be disregarded.
Example:
private bool _canTap = true;
public void YourMethod()
{
if(_canTap)
{
_canTap = false;
YourMethodToNavigate();
_canTap = true;
}
}
In the Icon_Pressed method add this,
this.IsEnabled = false;
await Navigation.PushAsync(new MyPage());
this.IsEnabled = true;
It disables the page until the current Icon pressed event is finished
This is known problem with Xamarin apps. I've used a private variable combined with a try-finally pattern to solve this. Ex:
bool allowTap = true;
public void ButtonTapped()
{
try
{
if(allowTap)
{
allowTap = false;
// Do whatever...
}
}
finally
{
allowTap = true;
}
}
The finally makes sure allowTap gets set back to true no matter what happens, short of a complete crash. Note that you can also use a catch block between the try and finally blocks to grab any errors if needed.

asp.net mobile/desktop site toggle button, switching masterpage, but styles "stuck"

Summary
I'm having style issues when flipping master pages via a button event in asp.net 4.0. The new master switches, but the css from the old master remains. I don't understand how this could happen as the styles are defined within the head of the old master, and i can clearly see via the markup the new master is being displayed with whats supposed to be a totally different set of styles. Also, viewing source shows all the new css declarations in the head. How can i get this to "refresh" or "reload"?
Some details
I'm implementing a mobile version of my asp.net site. If a mobile device is detected i set a cookie and switch the master page in the preinit to a mobile friendly one. This works fine:
protected virtual void Page_PreInit(Object sender, EventArgs e)
{
if (IsMobile)
this.Page.MasterPageFile = "m-" + this.Page.MasterPageFile;
}
I have a "full site" button at the bottom that allows you to flip back and forth between the mobile and desktop view. When clicking it, i change the value in the cookie. Then when the page redirects to itself, the value is checked, and it gives the respective masterpage. This also "works", i can tell the right masterpage is rendering via markup. Except the styles from the mobile version remain even when the desktop master is being displayed. I did the redirect thinking it would prevent this.
// desktop/mobile site toggle button click event
protected void viewMobileButton_Click(Object sender, EventArgs e)
{
HttpCookie isMobileCookie = Cookies.snatchCookie("isMobile");
if (bool.Parse(isMobileCookie.Value))
Cookies.bakeCookie("isMobile", "false");
else
Cookies.bakeCookie("isMobile", "true");
Response.Redirect(Request.RawUrl);
}
This is the first time I've done anything like this, and not sure if i'm even going about it the right way, or how to debug from here. Thanks in advance for any help.
Edit
Ok, so i figured out it's related to the JQuery Mobile Scripts. JQuery Mobile has this way of tying pages together. I don't fully understand it, i think they use it for page transitions, and it's preventing my new CSS from registering. When i turn it off, my masterpage flips fine with css included. I'm looking into a way to turn off JQuery Mobile before my redirect. Note sure how though yet.
The problem ended up being related to JQuery Mobile AJAX for page-transitions. JQuery Mobile does not load the head of the document on additional page requests after the first.
So when i'd switch the mobile master to the desktop master, the head of the document wouldn't load to bring in my styles. There are a few way's this can be fixed:
This way just turns off AJAX altogether, and fixes the problem, but then you can't benefit from it:
<form data-ajax="false">
This is a way to do it problematically, but remind you, it will not work via an event after initialization of JQuery Mobile, so again you can't benefit from it:
$.mobile.ajaxEnabled = false;
The above two solutions i support could work if you redirected through a page first if you have to use an onclick event and an event handler.
A better solution is to add rel="external" to the link to tell JQM it's and outgoing link.
<a href="myself.com?mobile=true" rel="external" >
But because i couldn't run some code i wanted to in order to change the cookie, i had to pass a query string parameter, check it on the preinit, then set the cookie which my page also looks at on the preinit and flips the master.
Here's my full solution below in case someone is out there doing the exact same thing. Note because my website is using aliasing, i had to read Request.RawUrl and parse it myself since the Request.QueryString object did not contain the values i passed.
// reusable function that parses a string in standard query string format(foo=bar&dave=awesome) into a Dictionary collection of key/value pairs
// return the reference to the object, you have to assign it to a local un-instantiated name
// will accept a full url, or just a query string
protected Dictionary<string, string> parseQueryString(string url)
{
Dictionary<string, string> d = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(url))
{
// if the string is still a full url vs just the query string
if (url.Contains("?"))
{
string[] urlArray = url.Split('?');
url = urlArray[1]; // snip the non query string business away
}
string[] paramArray = url.Split('&');
foreach (string param in paramArray)
{
if (param.Contains("="))
{
int index = param.IndexOf('=');
d.Add(param.Substring(0, index), param.Substring(++index));
}
}
}
return d;
}
Then i just use my dictionary object to evaluate and rebuild my url with the opposite mobile value, dynamically setting the href on the toggle link. Some code is obviosuly left out, but for perspective, base._iPage.QueryStringParams hold my dictionary object that was returned, and base._iPage.IsMobile is just a bool property i also have via the page interface i use, that all my pages, and user controls, ect, can talk to.
// get the left side fo the url, without querystrings
StringBuilder url = new StringBuilder(Request.RawUrl.Split('?')[0]);
// build link to self, preserving query strings, except flipping mobile value
if (base._iPage.QueryStringParams.Count != 0)
{
if (base._iPage.QueryStringParams.ContainsKey("mobile"))
{
// set to opposite of current
base._iPage.QueryStringParams["mobile"] = (!base._iPage.IsMobile).ToString();
}
int count = 0;
url.Append('?');
// loop through query string params, and add them back on
foreach (KeyValuePair<string, string> item in base._iPage.QueryStringParams)
{
count++;
url.Append(item.Key + "=" + item.Value + (count == base._iPage.QueryStringParams.Count ? "" : "&" ));
}
}
// assign rebuild url to href of toggle link
viewMobileButton.HRef = url.ToString();
}
Then on my pageinit this is where i actually check, first the quesry string, then the cookie, if neither of those are present, i run my mobile detection method, and set a cookie, and my interface bool property for easy access to conditionals that depends on it.
QueryStringParams = base.parseQueryString(Request.RawUrl);
if (QueryStringParams.ContainsKey("mobile") ? QueryStringParams["mobile"].ToLower().Equals("true") : false)
{
Cookies.bakeCookie("isMobile", "true"); // create a cookie
IsMobile = true;
}
else if (QueryStringParams.ContainsKey("mobile") ? QueryStringParams["mobile"].ToLower().Equals("false") : false)
{
Cookies.bakeCookie("isMobile", "false"); // create a cookie
IsMobile = false;
}
else
{
IsMobile = base.mobileDetection();
}
if (IsMobile)
this.Page.MasterPageFile = "m-" + this.Page.MasterPageFile;
}

ASP.NET - How can I add a new javascript from a user control loaded by callback?

I'm doing a menu that loads levels dynamicly, when you click on a item the next level is loaded asynchronously. For each menu item I have a user control. Every user control is declared in its parent, for example, the "secondlevelcontrol" has the reference to "thirdlevelcontrol".
With this level of nesting, I want to manage the asynchronous calls on every user control so, when the first level is loaded the javascript to load the second is loaded too. When the second level is loaded the javascript to load the third is loaded too.
To do asynchronous calls I'm implementing ICallbackEventHandler interface. As you can see in the examples, controls are added to the page as plain html. The method "ProcessOnLoadEvent" executes all lines of the "OnLoad" event of the user control.
An example of the implementation is this for the user control of fourth level:
public string GetCallbackResult()
{
return _callbackRendering;
}
public void RaiseCallbackEvent(string itemId)
{
var id = Int32.Parse(itemId);
var menu = new LateralMenu();
var currentChildren = menu.GetNodesById(id, 1);
var ctrl = this.Page.LoadControl(USER_CONTROL_FIVE_LEVEL_RELATIVE_PATH) as LeftSideFifthLevel;
ctrl.Items = currentChildren.Children;
ctrl.ProcessOnLoadEvent();
_callbackRendering = ctrl.GetHtml();
}
And this is the code for the fifth level user control:
public void ProcessOnLoadEvent()
{
EnsureChildControls();
if (null != RepeaterMenu)
{
SettingCallbackReference();
Visible = null != Items && 0 < Items.Count;
if (null != Items && 0 < Items.Count)
{
RepeaterMenu.DataSource = Items;
RepeaterMenu.DataBind();
}
}
}
public void RaiseCallbackEvent(string itemId)
{
var id = Int32.Parse(itemId);
var menu = new LateralMenu();
var currentChildren = menu.GetNodesById(id, 1);
var ctrl = this.Page.LoadControl(USER_CONTROL_SIX_LEVEL_RELATIVE_PATH) as LeftSideSixthLevel;
ctrl.Items = currentChildren.Children;
ctrl.ProcessOnLoadEvent();
_callbackRendering = ctrl.GetHtml();
}
public void SettingCallbackReference()
{
var cm = this.Page.ClientScript;
var cbRef = cm.GetCallbackEventReference(this, "itemId", "AnchorLevel5_OnClick_Callback", "ctx");
var cbScript = "function AnchorLevel5_OnClick(itemId, ctx){ new Menu().empty(ctx); " + cbRef + "; }";
cbScript += "function AnchorLevel5_OnClick_Callback(htmlText, ctx){ new Menu().render(htmlText, ctx); }";
cm.RegisterClientScriptBlock(this.GetType(), "CallServer", cbScript, true);
}
My problem is that levels beyond second level never work because the javascript associated with the user control ("SettingCallbackReference" method) has no html to put on the page.
Is there any way to create some user controls created dynamicly that implements ICallbackEventHandler interface that add new user controls to the page? Or, Am I doing something wrong and this is not the right way to implement this behaviour?
Thanks!!!
I suspect that you are probably going about this the wrong way. Take a look at the answer to this question: How to lazy load Infragistics UltraWebTree control?
This question was specifically about lazy-loading a tree view, but the same principles apply for lazy loading menu items. Follow these steps:
On first page load, render the top level menu
Include a function on the page that makes an ajax call to the server with the
parent id and retuns the next level items for that parent (getNodes in my example)
Bind this function to the click event of the top level menu items (that have sub items)
In the success handler of the ajax call, inject the returned menu items below
the parent and bind the same function to the click event of these items only.
On callback, be careful not to bind the function to the click event of ALL menu items, because then you will end up getting the function bound multiple times to the top level items and called multiple times. Just bind to the returned items.
Also, you need some way of determining that an item's sub items have already been loaded. That is what the following line in my example was for, but you might need something slightly different:
if (jQuery(nodesDiv).text() == 'Loading...') {
I used jQuery because it is the most concise, but you culd do this in pure js - I wouldn't recommend it.

Can I print an HTMLLoader (pdf) in Adobe Air?

I'm using AlivePDF to create a PDF file, then save it to the desktop. I can then use an HTMLLoader to display my lovely PDF file.
Now, the print button in Adobe Reader works fine. However, there will be young children using the app, so I'd like to have a big "Print" button right above it.
I figured I could just start up a print job and feed it my HTMLLoader. This won't work because the HTML loader rasterizes the content.
Any suggestions?
One answer I have found in order to solve this problem is called Cross-scripting PDF content. The idea is that a PDF can have embedded JavaScript, which can be called from the JavaScript within the HTML page "housing" said PDF (object tag only, no embed).
This site was of particular help. I had to simplify the JavaScript from that page down quite a bit. I kept getting syntax errors.
I also need my program to generate the PDF and the HTML content. I cannot ship a single PDF with embedded JS and an HTML file pointing to it. They need to be dynamically generated by the user. Here is a basic rundown:
private function printText(text:String):void
{
var p:PDF=new PDF(Orientation.PORTRAIT, Unit.MM, Size.LETTER);
p.addPage();
p.addText(text, 100, 100);
p.addJavaScript(this.getJavascript());
var f:FileStream=new FileStream();
var html:File=File.desktopDirectory.resolvePath("exported.html");
f.open(html, FileMode.WRITE);
f.writeUTF(this.getHtml());
f.close();
var file:File=File.desktopDirectory.resolvePath("exported.pdf");
f.open(file, FileMode.WRITE);
var bytes:ByteArray=p.save(Method.LOCAL);
f.writeBytes(bytes);
f.close();
Now that we have our two files, HTML and PDF, we can view the PDF, and create a giant purple print button for our younger / sight-impared users.
if (HTMLLoader.pdfCapability == HTMLPDFCapability.STATUS_OK)
{
var win:PrintTitleWindow; //the window w/giant button
var htmlLoader:HTMLLoader=new HTMLLoader();
var url:URLRequest=new URLRequest(html.url);
htmlLoader.width=880;
htmlLoader.height=(appHeight - 150); //i figure out the height elsewhere
htmlLoader.load(url);
var holder:UIComponent=new UIComponent();
holder.addChild(htmlLoader);
win=PrintTitleWindow(PopUpManager.createPopUp(mainWindow, PrintTitleWindow, true));
win.width=900;
win.height=(appHeight - 50);
win.addChild(holder);
win.addContent(htmlLoader);
PopUpManager.centerPopUp(win);
}
}
Here is the JS and HTML I used. I'm adding these in here for laughs. I'm sure there is a better way to do this, but I'm tired and it is late.
private function getJavascript():String
{
return 'function myOnMessage(aMessage) { print({ bUI: true, bSilent: false, bShrinkToFit: true }); } function myOnDisclose(cURL,cDocumentURL) { return true; } function myOnError(error, aMessage) { app.alert(error); } var msgHandlerObject = new Object(); msgHandlerObject.onMessage = myOnMessage; msgHandlerObject.onError = myOnError; msgHandlerObject.onDisclose = myOnDisclose; this.hostContainer.messageHandler = msgHandlerObject;';
}
private function getHtml():String
{
return '<html><head><script>function callPdfFunctionFromJavascript(arg) { pdfObject = document.getElementById("PDFObj");pdfObject.postMessage([arg]);}</script></head><body><object id="PDFObj" data="exported.pdf" type="application/pdf" width="100%" height="100%"></object></body></html>';
}
The PrintTitleWindow is a simple title window with the print button on it. The code to print is simple.
myHtmlLoader.window.callPdfFunctionFromJavascript('Print');
Eh Voila! I have a gigantor-print-button like so:
(source: cetola.net)
Hitting that big purple print button is the same as hitting the print icon in the PDF toolbar. The difference for me is that my users, who could be elementary or middle-school kids, won't have to look around for the stupid button.
So, it's a long way around the block. Still, if you need to print and can't rely on that adobe toolbar button, here's your answer :) .

Resources