How do I initialise a NativeScript app fully programmatically (without XML)? - nativescript

Here's what I have so far. The background goes green (the colour of the Page), but I'd expect a purple ContentView with some text inside to fill the page, too.
Is there anything further I'm missing?
import { on, run, launchEvent } from "tns-core-modules/application";
import { Frame } from "tns-core-modules/ui/frame/frame";
import { ContentView } from "tns-core-modules/ui/content-view/content-view";
import { TextBase } from "tns-core-modules/ui/text-base/text-base";
import { Page } from "tns-core-modules/ui/page/page";
on(launchEvent, (data) => {
const frame = new Frame();
const page = new Page();
page.backgroundColor = "green";
const contentView = new ContentView();
const textBase = new TextBase();
contentView.height = 100;
contentView.width = 100;
contentView.backgroundColor = "purple";
textBase.text = "Hello, world!";
contentView._addView(textBase);
page.bindingContext = contentView;
frame.navigate({ create: () => page });
data.root = page; // Incidentally, should this be the frame or the page?
});
run();

You are almost on track, you just need slight modification on your code.
import { on, run, launchEvent } from 'tns-core-modules/application';
import { Frame } from 'tns-core-modules/ui/frame/frame';
import { ContentView } from 'tns-core-modules/ui/content-view/content-view';
import { TextField } from 'tns-core-modules/ui/text-field';
import { Page } from 'tns-core-modules/ui/page/page';
run({
create: () => {
const frame = new Frame();
frame.navigate({
create: () => {
const page = new Page();
page.backgroundColor = "green";
const contentView = new ContentView();
const textField = new TextField();
contentView.height = 100;
contentView.width = 100;
contentView.backgroundColor = "purple";
textField.text = "Hello, world!";
contentView.content = textField;
page.content = contentView;
return page;
}
});
return frame;
}
});
You don't have to wait for launch event, you could set the root frame in run method itself.
In your code, you were creating the frame but never adding it to root UI element or mark the frame itself as root element
It's recommended to use .content to add child for a ContentView / Page as they are originally designed to hold one child element only.
Use TextField / TextView for input text, TextBase is just a base class.

It seems to me that you try to overcomplicate. You can replace XML with code just by implementing createPage method - Create a page via code.
I just modified default NS + TypeScript Playground template to operate without XML - NS + TypeScript template without XML.

I think you can't leave run as empty as it is expecting an entry to start the app. From {NS} website,
You can use this file to perform app-level initializations, but the
primary purpose of the file is to pass control to the app's root
module. To do this, you need to call the application.run() method and
pass a NavigationEntry with the desired moduleName as the path to the
root module relative to your /app folder.
if you look for run code in "tns-core-modules/application"
function run(entry) {
createRootFrame.value = false;
start(entry);
}
exports.run = run;
and
function start(entry) {
if (started) {
throw new Error("Application is already started.");
}
started = true;
mainEntry = typeof entry === "string" ? { moduleName: entry } : entry;
if (!androidApp.nativeApp) {
var nativeApp = getNativeApplication();
androidApp.init(nativeApp);
}
}

Related

How can I set the font-family of large titles on iOS?

By attaching an event listener to the loaded-event on Page and getting the NativeView-object I've been able to set the prefersLargeTitle to true:
loaded(event){
const page = event.object;
if (isIOS) {
page.frame.ios.controller.navigationBar.prefersLargeTitles = true;
}
}
This works, but I would like to change the font-family of the large title. How can I do this in Nativescript?
Try adding the code below to your app.js
import {
isIOS
} from "tns-core-modules/platform";
import {
ActionBar
} from "tns-core-modules/ui/action-bar";
import {
Font
} from "tns-core-modules/ui/styling/font";
if (isIOS) {
ActionBar.prototype.originalSetColor = ActionBar.prototype.setColor;
ActionBar.prototype.setColor = function (navBar, color) {
ActionBar.prototype.originalSetColor.call(this, navBar, color);
var newDict = {
[NSFontAttributeName]: Font
.default
.withFontFamily(
"yourFontFamily")
.withFontSize(yourFontSize)
.getUIFont(UIFont
.systemFontOfSize(20)),
};
if (navBar.largeTitleTextAttributes) {
newDict[NSForegroundColorAttributeName] = navBar.largeTitleTextAttributes.valueForKey(NSForegroundColorAttributeName);
}
navBar.largeTitleTextAttributes = newDict;
};
}
You may still set the prefersLargeTitles flags as you are doing already.

Transparent view

I would like to create a View (stage, window) with partially transparent background. I have an image containing alpha channel
I used this kind of scenes in JavaFx, where I had to set the scene fill to null and the root node background color to transparent. I tried the same with TornadoFX:
class NextRoundView : View("Következő kör") {
override val root = vbox {
style {
backgroundColor = multi(Color.TRANSPARENT)
backgroundImage = multi(URI.create("/common/rope-bg-500x300.png"))
backgroundRepeat = multi(BackgroundRepeat.NO_REPEAT
to BackgroundRepeat.NO_REPEAT)
}
prefWidth = 500.0
prefHeight = 300.0
spacing = 20.0
padding = insets(50, 20)
text("A text") {
font = Font.font(40.0)
alignment = Pos.CENTER
}
button("OK")
{
font = Font.font(20.0)
action {
close()
}
}
sceneProperty().addListener{ _,_,n ->
n.fill = null
}
}
}
I'm calling the view like this:
NextRoundView().apply {
openModal(stageStyle = StageStyle.TRANSPARENT, block = true)
}
However, the stage is still has background:
What have I missed?
You've made a couple of mistakes that causes this. First of all, you must never manually instantiate UICompoenents (View, Fragment). Doing so will make them miss important life cycle callbacks. One important callback is onDock, which is the perfect place to manipulate the assigned scene. Changing these two issues and also cleaning up some syntax leads to this code, which successfully makes the background transparent:
class MyApp : App(MyView::class)
class MyView : View() {
override val root = stackpane {
button("open").action {
find<NextRoundView>().openModal(stageStyle = StageStyle.TRANSPARENT, block = true)
}
}
}
class NextRoundView : View("Következő kör") {
override val root = vbox {
style {
backgroundColor += Color.TRANSPARENT
backgroundImage += URI.create("/common/rope-bg-500x300.png")
backgroundRepeat += BackgroundRepeat.NO_REPEAT to BackgroundRepeat.NO_REPEAT
}
prefWidth = 500.0
prefHeight = 300.0
spacing = 20.0
padding = insets(50, 20)
text("A text") {
font = Font.font(40.0)
alignment = Pos.CENTER
}
button("OK") {
font = Font.font(20.0)
action {
close()
}
}
}
override fun onDock() {
currentStage?.scene?.fill = null
}
}
Here is a screenshot of the app with the changes implemented:

Xamarin TabbedPage Performance When Using Children.Add

Good Afternoon,
I have a question regarding the performance of the tabbed page and whether there is a more efficient way to load the tabs into the page, without any frame loss or process hang.
Regarding this issue, I have been having a few issues with TabbedPage in Xamarin.Forms. My project currently consists of a Listview with 104 components. When I click on one of the items it opens up a tabbed page consisting of 3 tabed pages. I start the tabbed page using the following code
int Clicked = 0;
public async Task CheckClick(Page data)
{
Clicked += 1;
if (Clicked == 1)
{
await Navigation.PushAsync(data);
List_View.SelectedItem = null;
}
}
public async void OnSelection(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null)
{
return;
}
var Cell = e.SelectedItem as DataSource;
switch (Cell.ID)
{
case 0:
await CheckClick(new Function_One());
break;
}
}
The real problem comes after, whether I pre load the information ahead of time or do what ever, whenever I call Children.Add(data); the whole application hangs for 1 or 2 seconds and than allows the page to load. The code is as follows.
public class Function_One : TabbedPage
{
private async Task Test()
{
//
var data = new NewPageData("", "", "OP.png", "Persian.png", Description, Usage, Storage, Data);
var data2 = new NewMedicalPage(Medical_Info, Translation, startinfo);
var data3 = new NewNotePage("", 0);
data.Icon = "Info.png";
await Task.Delay(100);// Little await to load the page first before hang
Device.BeginInvokeOnMainThread(async () =
{
Children.Add(data);// HERE is the freeze
Children.Add(data2);// these
Children.Add(data3);// 3 (Adding of the Tabs)
});
}
public void UpdateData()
{
Task.Run(async () =
{
try
{
Description.Spans.Add(new Span
{
Text = "Data.... ",
FontSize = 18,
FontFamily = variables.fontFamily
});
Usage.Spans.Add(new Span
{
Text = "MoreData...",
FontSize = 18,
FontFamily = variables.fontFamily
});
Storage.Spans.Add(new Span
{
Text = "LastData...",
FontSize = 18,
FontFamily = variables.fontFamily
});
await Test();
}
catch { }
}).ConfigureAwait(false);
}
public Function_One()
{
UpdateData();
BarBackgroundColor = MainColor;
BarTextColor = Color.WhiteSmoke;
Title = "Page";
}
}
NOTE: (This Project Is Completely An Offline Project, No Internet Needed)
If anyone could explain to me a better method for loading the data while keeping the application flowing and smooth, that would be appreciated. The data does not have to load all at once, just as long as the page opens as soon as its clicked!

QML Loader not shows changes on .qml file

I have main.qml and dynamic.qml files that i want to load dynamic.qml on main.qml using Loader {}.
Content of dynamic.qml file is dynamic and another program may change its content and overwrite it.
So i wrote some C++ code for detecting changes on file and fires Signal.
My problem is that I don't know how can i force Loader to reload file.
This is my current work:
MainController {
id: mainController
onInstallationHelpChanged: {
helpLoader.source = "";
helpLoader.source = "../dynamic.qml";
}
}
Loader {
id: helpLoader
anchors.fill: parent
anchors.margins: 60
source: "../dynamic.qml"
}
I think that QML Engine caches dynamic.qml file. So whenever I want to reload Loader, it shows old content. Any suggestion?
You need to call trimComponentCache() on QQmlEngine after you have set the Loaders source property to an empty string. In other words:
helpLoader.source = "";
// call trimComponentCache() here!!!
helpLoader.source = "../dynamic.qml";
In order to do that, you'll need to expose some C++ object to QML which has a reference to your QQmlEngine (lots of examples in Qt and on StackOverflow to help with that).
trimComponentCache tells QML to forget about all the components it's not current using and does just what you want.
Update - explaining in a bit more detail:
For example, somewhere you define a class that takes a pointer to your QQmlEngine and exposes the trimComponentCache method:
class ComponentCacheManager : public QObject {
Q_OBJECT
public:
ComponentCacheManager(QQmlEngine *engine) : engine(engine) { }
Q_INVOKABLE void trim() { engine->trimComponentCache(); }
private:
QQmlEngine *engine;
};
Then when you create your QQuickView, bind one of the above as a context property:
QQuickView *view = new QQuickView(...);
...
view->rootContext()->setContextProperty(QStringLiteral("componentCache", new ComponentCacheManager(view->engine());
Then in your QML you can do something like:
helpLoader.source = "";
componentCache.trim();
helpLoader.source = "../dynamic.qml";
I was hoping for a pure QML solution. I noticed that loader.source is a url (file:///) and remembered how with HTML, you can avoid HTTP caching using ?t=Date.now() in your requests. Tried adding ?t=1234 to the end of loader.source, and sure enough, it works.
import QtQuick 2.0
Item {
Loader {
id: loader
anchors.fill: parent
property string filename: "User.qml"
source: filename
function reload() {
source = filename + "?t=" + Date.now()
}
}
Timer {
id: reloadTimer
interval: 2000
repeat: true
running: true
onTriggered: {
loader.reload();
}
}
}
I also wrote another example that will check for changes in the file contents before triggering a reload using an XMLHttpRequest.
import QtQuick 2.0
Item {
Loader {
id: loader
anchors.fill: parent
property string filename: "AppletUser.qml"
property string fileContents: ""
source: ""
function reload() {
source = filename + "?t=" + Date.now()
}
function checkForChange() {
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.readyState === 4) {
if (loader.fileContents != req.responseText) {
loader.fileContents = req.responseText;
loader.reload();
}
}
}
req.open("GET", loader.filename, true);
req.send();
}
onLoaded: {
console.log(source)
}
Timer {
id: reloadTimer
interval: 2000
repeat: true
running: true
onTriggered: loader.checkForChange()
}
Component.onCompleted: {
loader.checkForChange()
}
}
}

How to add images dynamically in AS3?

Ok, i want to add a series of images, and then be able to drag and drop each one of them. I have all of my images embedded in a class Images. s0,s1,s2 are instances of image classes. Now this is what i've done
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
function init(e:Event = null):void
{
var a:Array = new Array();
var imageContainer:Sprite = new Sprite;
var imgClass:Images = new Images();
for (var i:int = 0; i < 9; i++) {
a[i].push(imgClass.(s+String(i)));
imageContainer.addChild[a[i]];
}
stage.addChild(imageContainer);
imageContainer.addEventListener(MouseEvent.MOUSE_UP, takeIt);
imageContainer.addEventListener(MouseEvent.MOUSE_DOWN, dropIt);
function takeIt(event:MouseEvent) {
event.currentTarget.startDrag();
}
function dropIt(event:MouseEvent) {
event.currentTarget.stopDrag();
}
}
}
I have rewritten your code a bit, but I have not been able to test it sorry.
The first think I notices is that your init method was inside your Main method. Then your takeIt and dropIt method were inside the init. I am not to sure if that would actually work, but I have fixed it up in the code below.
In my code I assume that the image instances you have in the Images class are instances of Bitmap. This means that inside the for loop I had to add each one to a Sprite so you have access to startDrag and stopDrag. I listen for the MOUSE_DOWN event on each image, and set the image to the selectedImage var and do startDrag. I also listen for the MOUSE_UP on the stage, and drop the currentImage.
package
{
import flash.display.Bitmap;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class Main extends Sprite
{
private var selectedImage:Sprite;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
var imageContainer:Sprite = new Sprite();
var imgClass:Images = new Images();
for (var i:int = 0; i < 9; i++)
{
var imgInstance:Bitmap = imgClass['s' + i.toString()] as Bitmap;
var imgSprite:Sprite = new Sprite();
imgSprite.addChild(imgInstance); // Put image in a sprite so we can use startDrag on it.
imageContainer.addChild(imgSprite);
imgInstance.addEventListener(MouseEvent.MOUSE_DOWN, img_mouseDownHandler);
}
addChild(imageContainer);
stage.addEventListener(MouseEvent.MOUSE_UP, stage_mouseUpHandler);
}
private function dropSelectedImage():void
{
if (selectedImage)
{
selectedImage.stopDrag();
}
}
private function img_mouseDownHandler(e:MouseEvent):void
{
dropSelectedImage();
selectedImage = e.currentTarget as Sprite;
selectedImage.startDrag();
}
private function stage_mouseUpHandler(e:MouseEvent):void
{
dropSelectedImage();
}
}
}
To build your images in a loop you could also just manually build the array, then loop through the array like so:
var images:Array = [imgClass.s0, imgClass.s1, imgClass.s2, imgClass.s3, imgClass.s4, imgClass.s5, imgClass.s6, imgClass.s7, imgClass.s8];
for each (var img:Bitmap in images)
{
var imgSprite:Sprite = new Sprite();
imgSprite.addChild(img); // Put image in a sprite so we can use startDrag on it.
imageContainer.addChild(imgSprite);
imgInstance.addEventListener(MouseEvent.MOUSE_DOWN, img_mouseDownHandler);
}
Hope this help. Feel free to ask me to elaborate or explain anything else in the comments.

Resources