How to update ItemFragment when item property changes - kotlin

I'm quite confused about the relationship between an ItemViewModel and its underlying item. I'm trying to show a badge on the screen, and then if the badge is "achieved" on another thread, the badge should update on screen.
Right now, the badge code looks like this:
class Badge {
val achievedProperty = SimpleBooleanProperty(this, "achieved", false)
var achieved by achievedProperty
}
class BadgeModel: ItemViewModel<Badge>() {
val achieved = bind(Badge::achievedProperty)
}
Next, I want to display it on the screen, in an ItemFragment subclass:
class BadgeFragment(badge: Badge): ItemFragment<Badge>() {
private val model: BadgeModel by inject()
init {
model.item = badge
}
override val root = rectangle {
width = 50
height = 50
stroke = Color.BLACK
fill = if (model.achieved) Color.RED else Color.GREEN
}
}
This works fine to start, but if I then set badge.achieved = true (in my controller), the color of the badge on screen doesn't change.
I'm clearly missing something about the relationship between the object and the model, but I'm having a lot of trouble figuring it out from documentation. Can someone help me get my fragment working the way I want it to?

Your problem isn't specific to models or almost anything TornadoFX-related. The way you've written it only checks for the color once upon creation. You need to use properties or bindings to listen to property changes:
class Test : Fragment() {
val modelAchieved = SimpleBooleanProperty(true) // pretend this is model.achieved
val achievedColor = modelAchieved.objectBinding { isAchieved ->
if (isAchieved == true) Color.RED
else Color.GREEN
}
override val root = vbox {
togglebutton("Toggle") {
selectedProperty().bindBidirectional(modelAchieved)
}
rectangle(width = 50, height = 50) {
stroke = Color.BLACK
fillProperty().bind(achievedColor)
}
}
}
I would also bidirectionally bind the item properties of the viewmodel and fragment together so they stay in sync. Or just use a regular fragment, and reference the model's item property by itself.

Related

How can I access programmatically created UI Elements in Xamarin.Forms : timepicker

I got a method which is creating UI Element programmatically. Example:
Label label1 = new Label();
label1.Text = "Testlabel";
TimePicker timepicker1 = new TimePicker();
timepicker1.Time = new TimeSpan(07, 00, 00);
After that I add both of them in an already existing StackLayout.
stacklayout1.Children.Add(label1);
stacklayout1.Children.Add(timepicker1);
A user of my app can create this multiple times.
Now my question is, how can I access, for example, the second/ better all of TimePickers which were created?
Some suggestions:
Use ID:
var id = label1.Id;
var text = (stacklayout1.Children.Where(x => x.Id == id).FirstOrDefault() as myLabel).Text;
Use index:
Label labelOne = stacklayout1.Children[0] as Label;
Use tag:
Create a custom property tag to the label:
public class myLabel : Label{
public int tag { get; set; }
}
And find label by the tag:
var labels = stacklayout1.Children.Where(x => x is myLabel).ToList();
foreach (myLabel childLabel in labels)
{
if (childLabel.tag == 0)
{
}
}
BTW, if you create the label in xaml, you can use findbyname:
Label label = stacklayout1.FindByName("labelA") as Label;
var timepickers = stacklayout1.Children.Where(child => child is TimePicker);
Will return an IEnumerable of all of the timepickers added to your StackLayout. You will have to also add using System.Linq to your usings at the top of the page.

Change dynamically border color of an agent in Repast Symphony

I have to change dynamically the border color of an agent. The agent is represented as the default circle on the display. The displayed color has to change according to a boolean variable defined inside the agent Class.
When the agent is created and displayed for the first time, it has the correct style but when the boolen variable inside the agent class changes, the border color does not change.
If I do the same for the fill color of the agent instead, it works fine. I put here the code I used:
public class NodeStyle extends DefaultStyleOGL2D{
#Override
public Color getBorderColor(Object agent) {
Color borderColor = Color.BLACK;
if(agent instanceof Process) {
Process p = (Process)agent;
if(p.isParticularNode) {
borderColor = Color.RED;
}
}
return borderColor;
}
}
When the agent is created and it is added to the context, it take the correct color but if isParticularNode changes, the border color does not change.
I've tryed also to do the same importing the interface StyleOGL2D but the problem remains
I tried this with the JZombies demo, adding an "id" double to each zombie that is set with RandomHelper.nextDouble() each tick. The border color changes as expected. By default the border size is 0, so perhaps that needs to be changed in your code.
public class ZombieStyle extends DefaultStyleOGL2D {
public Color getColor(Object agent) {
return Color.RED;
}
public Color getBorderColor(Object agent) {
Zombie z = (Zombie)agent;
if (z.getID() > 0.5) {
return Color.GREEN;
}
return Color.black;
}
public int getBorderSize(Object agent) {
return 4;
}
}

TornadoFX - remove item with ContextMenu right click option

So I have a table view that displays an observedArrayList of AccountsAccount(name, login, pass), those are data classes. When I right click a cell there pops an option of delete. What I want to do is delete that Account from the observedArrayList
Only I can not find any way to do this. I am not experienced with JavaFX or TornadoFX and I also can't find the answer with google or in the TornadoFX guides and docs.
This is my code:
class ToolView : View() {
override val root = VBox()
companion object handler {
//val account1 = Account("Google", "martvdham#gmail.com", "kkk")
//val account2 = Account("Google", "martvdham#gmail.com", "Password")
var accounts = FXCollections.observableArrayList<Account>(
)
var gson = GsonBuilder().setPrettyPrinting().create()
val ggson = Gson()
fun writeData(){
FileWriter("accounts.json").use {
ggson.toJson(accounts, it)
}
}
fun readData(){
accounts.clear()
FileReader("accounts.json").use{
var account = gson.fromJson(it, Array<Account>::class.java)
if(account == null){return}
for(i in account){
accounts.add(i)
}
}
}
}
init {
readData()
borderpane {
center {
tableview<Account>{
items = accounts
column("Name", Account::name)
column("Login", Account::login)
column("Password", Account::password)
contextMenu = ContextMenu().apply{
menuitem("Delete"){
selectedItem?.apply{// HERE IS WHERE THE ITEM DELETE CODE SHOULD BE}
}
}
}
}
bottom{
button("Add account").setOnAction{
replaceWith(AddView::class, ViewTransition.SlideIn)
}
}
}
}
}
Thanks!
To clarify #Martacus's answer, in your case you only need to replace // HERE IS WHERE THE ITEM DELETE CODE SHOULD BE with accounts.remove(this) and you're in business.
You could also replace the line
selectedItem?.apply{ accounts.remove(this) }
with
selectedItem?.let{ accounts.remove(it) }
From my experience, let is more common than apply when you are just using a value instead of setting up a receiver.
Note that the process will be different if the accounts list is constructed asynchronously and copied in, which is the default behavior of asyncItems { accounts }.
selectedItem is the item you have selected/rightclicked.
Then you can use arraylist.remove(selectedItem)

How i get back to old scene in SpriteKit?

How can I go to old scene in SpriteKit ( in Swift ) ? I am in scene1 , i paused it and i move to scene 2 and I want to get back to scene1 and i to resume it . I don't want to recreate it again .
Thanks.
EDIT :
So , I tried this :
In secondScene class i put a oldScene SKScene property and a computed setOldScene property:
class SecondScene: SKScene {
var oldScene:SKScene?
var setOldScene:SKScene? {
get { return oldScene }
set { oldScene = newValue }
}
init(size: CGSize) {
super.init(size: size)
/* Setup my scene here */
}}
And then , in FirstScene class ( where i have ONE moving ball ) , in touchBegan method i have this
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if(self.view.scene.paused) {
self.view.scene.paused = false
}
else {
self.view.scene.paused = true
}
var secondScene = SecondScene(size: self.frame.size)
secondScene.setOldScene = self.scene
if !transition { self.view.presentScene(secondScene, transition: SKTransition.doorsOpenHorizontalWithDuration(2)) ; transition = true }
}
}
But , when i call self.view.presentScene(oldScene) in SecondScene class i move yo first class and i have TWO balls that moving :-??
I can't use class variable , Xcode not suport yet .
I solved this issue by having a public property on the second scene.
And then using that to present the first scene.
So in the First Scene, when you want to show the second scene....
if let scene = SKScene(fileNamed: "SecondScene") as! SecondScene?
{
scene.parentScene = self
self.view?.presentScene(scene)
}
This following code is in the SecondScene.
When ready to return to the first scene, just call the method showPreviousScene()...
public var parentScene: SKScene? = nil
private func showPreviousScene()
{
self.view?.presentScene(parentScene!)
}
You will have to keep a reference to your old scene around, and then when you want to move back to it, simply call
skView.presentScene(firstScene)
on your SKView instance.
There are several ways to keep a reference to your class around. You could use a Singleton, or simply a class with a class type property to store the reference and access it from anywhere.

AutoCompleteTextView OnItemClickListener null param (landscape mode on HTC Desire S)

My Problem : I have an AutoCompleteTextView with an OnItemClickListener. This has been working fine for 18 months, but I have now noticed it throws a NullPointerException when I select an item in landscape mode on my HTC Desire S. (There is no error in portrait mode or on any other phone or emulator I've tested it on).
The AdapterView<?> av parameter is coming through as null. Why would this be, and how can I get around it?
Code :
myAutoCompleteTextView = (AutoCompleteTextView) findViewById(R.id.myAutoCompleteTextView);
myAutoCompleteTextView.setSingleLine();
myAutoCompleteTextView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> av, View v, int index, long arg) {
String selectedItem = (String)av.getItemAtPosition(index);
//Do stuff with selected item ...
}
}
Error :
java.lang.NullPointerException
at uk.co.myCompany.mobile.android.myCompanymobile.pages.groups.AbstractGroupSelectionPage$3.onItemClick(AbstractGroupSelectionPage.java:228)
at android.widget.AutoCompleteTextView.onCommitCompletion(AutoCompleteTextView.java:993)
at com.android.internal.widget.EditableInputConnection.commitCompletion(EditableInputConnection.java:76)
at com.android.internal.view.IInputConnectionWrapper.executeMessage(IInputConnectionWrapper.java:368)
at com.android.internal.view.IInputConnectionWrapper$MyHandler.handleMessage(IInputConnectionWrapper.java:86)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:4385)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:849)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:607)
at dalvik.system.NativeStart.main(Native Method)
Extra Code - my custom adapter inner class :
/**
* An inner class to simply make a custom adapter in which we can alter the on-screen look of selected groups.
*/
private class SelectedGroupAdapter extends ArrayAdapter<Group> {
private ArrayList<Group> items;
private int layout;
public SelectedGroupAdapter(Context context, int layout, ArrayList<Group> items) {
super(context, layout, items);
this.items = items;
this.layout = layout;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(layout, null);
}
Group o = items.get(position);
//Display the group name and number of contacts
if (o != null) {
TextView groupName = (TextView) v.findViewById(R.id.groupName);
TextView noOfContacts = (TextView) v.findViewById(R.id.noOfContacts);
if (groupName != null) {
groupName.setText(o.getGroupName());
}
if(noOfContacts != null) {
if (o.isDynamic())
noOfContacts.setText(getString(R.string.dynamic));
else {
int contactsCount = o.getGroupSize();
if(contactsCount == 1) noOfContacts.setText(contactsCount + " " + getString(R.string.contact));
else noOfContacts.setText(contactsCount + " " + getString(R.string.contacts));
}
}
}
return v;
}
}
My hunch is that since you are declaring android:configChanges="orientation" in your manifest, then when you rotate the old OnItemClickListener is still sticking around, and since you technically have a new layout, the AdapterView that was being used prior to orientation change doesn't exist anymore, thus is null when you click on an item.
There's 2 things I think that would solve this if this is the case:
Remove the orientation option in your manifest. Any events you place in configChanges tells Android "I'm taking care of this configuration change, so let me handle it" as opposed to letting Android handle it. The normal operation for Android in the event of an orientation change is to destroy and recreate your Activity (it will take care of repopulating some Views with data automatically).
If you decide you need to handle orientation changes, then override onConfigurationChanged() and set the OnItemClickListener to the new AdapterView object (ListView, GridView, whichever you are using) that should have been recreated in the onCreate method.