I have a GridPane (6x9) made with SceneBuilder. Inside that GridPane I created 50 ToggleButtons in my controller with the following code:
#FXML
private void initialize() {
Font numbersFont = new Font("Arial", 10);
for (int y = 0; y < 9; y++){
for (int x = 1; x < 7; x++) {
Integer buttonId = (y * 6) + x;
if (buttonId == 51) {break;}
ToggleButton tb = new ToggleButton(Integer.toString(buttonId));
tb.setId("tb_" + Integer.toString(buttonId));
tb.setFont(numbersFont);
tb.setPrefSize(27, 27);
tb.setAlignment(Pos.CENTER);
tb.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
boolean isSelected = newValue;
System.out.println(isSelected);
}
});
numbersGrid.add(tb, x - 1, y);
}
}
}
In my MainApp.java I've created an array of integers:
private ObservableIntegerArray selectedNumbers = FXCollections.observableIntegerArray();
I have two questions.
I want is to update the selectedNumbers array with the numbers of the ToggleButtons that are pressed. For example, if I press button 1, 13 and 25, the array should be [1, 13, 25]. When I "turn off" a button, that number should be removed from the array. How can I add and remove those values? Where should I have this code? Inside the initialize method?
I have a method that randomly picks N numbers from the 50 available (1 to 50). When the user clicks a button, I want the ToggleButtons with that number to be "turned on". I know how to do a loop but how can I access the buttons? I mean, I named them tb_1, tb_2 and so on. So... how can I call them?
Thanks!
I would recommend creating and exposing an ObservableSet<Integer> in the controller, representing the set of selected toggles. This is pretty straightforward to do using the code you already have:
public class MyController {
private final ObservableSet<Integer> selectedToggles = FXCollections.observableSet();
public final ObservableSet<Integer> getSelectedToggles() {
return selectedToggles ;
}
#FXML
private void initialize() {
Font numbersFont = new Font("Arial", 10);
for (int y = 0; y < 9; y++){
for (int x = 1; x < 7; x++) {
Integer buttonId = (y * 6) + x;
if (buttonId == 51) {break;}
ToggleButton tb = new ToggleButton(Integer.toString(buttonId));
tb.setId("tb_" + Integer.toString(buttonId));
tb.setFont(numbersFont);
tb.setPrefSize(27, 27);
tb.setAlignment(Pos.CENTER);
tb.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
boolean isSelected = newValue;
if (isSelected) {
selectedToggles().add(buttonId);
} else {
selectedToggles().remove(buttonId);
}
}
});
numbersGrid.add(tb, x - 1, y);
}
}
}
}
Then in your main application (or wherever you need) you can retrieve the observable set and register a listener with it:
FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/fxml"));
Parent parent = loader.load();
MyController controller = loader.getController();
controller.getSelectedToggles().addListener((Change<? extends Integer> change) -> {
ObservableSet<Integer> selectedToggles = controller.getSelectedToggles();
// process selected ids...
});
To be able to manipulate the toggle buttons state (e.g. to initialize them to your random values), you can make each button respond to changes in the observable set. In the code where you create the toggle button (in the controller), add this:
// existing code:
ToggleButton tb = new ToggleButton(Integer.toString(buttonId));
tb.setId("tb_" + Integer.toString(buttonId));
// respond to changes in observable set:
selectedToggles.addListener((Change<? extends Integer> change) ->
tb.setSelected(selectedToggles.contains(buttonId)));
Then you can manipulate the toggle button state with
List<Integer> initiallySelected = ... ;
controller.getSelectedToggles().clear();
controller.getSelectedToggles().addAll(initiallySelected);
1:
Don't use arrays like that. If doing something like that, use an ArrayList!
I suggest using an array with 50 boolean indices boolean[] myArray = boolean[50]; and setting the indices true or false.
You would have it loop over the entire array and use the array's values on initialization. You then add an event listener to the buttons to run it again whenever the user toggles a value.
2:
Add all your buttons to an array and get them by index. As they will be referenced (and not copied) this will have no impact on performance, perhaps the memory usage increases by a few kilobits, but that'll be it.
Related
When a component is hidden, its height is forced to 0 which prevents the scrolling in Component to work when you drag it downwards. Basically, it can be dragged down to edge of the screen, but then no scrolling down happens. This issue was also mentioned earlier in SO here where Shai offered a path for investigation.
Any suggestions for a way to fix this issue?
Thanks
Here's a code sample to reproduce the issue (copy/paste into a Hello World project in replacement of the default start() method):
Image dragImage2;
Component dropPlaceholder;
void saveDragImage(Image dragIm) {
dragImage2 = dragIm;
}
public void start() {
if (current != null) {
current.show();
return;
}
/* To seee the issue: press and hold a button about 1 second to initiate the drag and drop,
then drag down to the lower edge of the screen: the list doesn't scroll down as normally.
When dragging upwards in a list (which must of course alreaday be scrolled down a bit),
it works as expected: When the dragged button reaches to top of the screen, the list scrolls down. */
Form hi = new Form("Hi World", new BorderLayout());
dropPlaceholder = new Label(" ... "); //used to create an empty space in the drop position
dropPlaceholder.setDropTarget(true);
Container list3 = new Container(BoxLayout.y());
for (int i = 0; i < 30; i++) {
Button draggableButton = new Button("Button number " + i) {
#Override
public void longPointerPress(int x, int y) {
setDraggable(true);
setFocusable(true);
dragImage2 = super.getDragImage();
setHidden(true);
Form f = getComponentForm();
f.pointerPressed(x, y);
pointerDragged(x - 1, y - 1); //move the dragged element a few pixels to initiate the drag
pointerDragged(x - 2, y - 2);
}
#Override
protected Image getDragImage() {
return dragImage2;
}
#Override
protected void dragFinished(int x, int y) {
setHidden(false);
if (dropPlaceholder != null)
dropPlaceholder.remove(); //remove the placeholder after drag
}
};
draggableButton.setDropTarget(true);
draggableButton.addDragOverListener((e) -> {
Component dropTarget = e.getDropTarget(); //may be null (in Component.dragFinishedImpl(int x, int y))
if (dropTarget != null) {
Container parent = dropTarget.getParent();
int index = parent.getComponentIndex(dropTarget);
dropPlaceholder.remove(); //remove placeholder from previous position
parent.addComponent(index, dropPlaceholder); //add placeholder at position of dropTarget
}
getCurrentForm().revalidate(); //refresh the screen to show the new position of placeholder
});
list3.add(draggableButton);
}
list3.setScrollableY(true);
Container cont = hi.getContentPane();
cont.add(BorderLayout.CENTER, list3);
hi.show();
}
I am using vaadin 7.7.7
In a grid i have a combobox as an edited item in one of the columns
as
grid.addColumn("columnProperty").setEditorField(combobox);
I need to update a property/cell in same row based on the combobox selection change
My issue is , the selection change event triggers twice, once when the combobox in clicked and second when the selection value is changed. But the updated value in next cell gets reflected on UI only first time.
Below is the code written . Any solutions?
Combobox.addValueChangeListener(new ValueChangeListener()
#Override
public void valueChange(ValueChangeEvent event) {
// below line works only first time when the combobox is clicked,but i want
//it when the item in the combobox is changed
gridContainer.getContainerProperty(editedRow,"editedColumProperty").setValue("ValueTobeUpdated");}
});
Need to update the unit column on combobox change in edited mode(before saving)
Refer below link for image
example image
You will get value change events even when the field gets value that it should show to the user. In order to get event that indicates that the user has accepted the input you should use field group (setEditorFieldGroup).
From Book of Vaadin example for grid editing:
grid.getColumn("name").setEditorField(nameEditor);
FieldGroup fieldGroup = new FieldGroup();
grid.setEditorFieldGroup(fieldGroup);
fieldGroup.addCommitHandler(new CommitHandler() {
private static final long serialVersionUID = -8378742499490422335L;
#Override
public void preCommit(CommitEvent commitEvent)
throws CommitException {
}
#Override
public void postCommit(CommitEvent commitEvent)
throws CommitException {
Notification.show("Saved successfully");
}
});
Edit
I assume that you want to connect Parameter and Unit comboboxes. I would do that with this kind of value change lister
BeanItemContainer container = new BeanItemContainer<>(
Measurement.class,
measurements);
Grid grid = new Grid(container);
grid.setEditorEnabled(true);
ComboBox parameterComboBox = new ComboBox();
ComboBox unitComboBox = new ComboBox();
parameterComboBox.addItems(Parameter.Pressure, Parameter.Temperature, Parameter.Time);
parameterComboBox.addValueChangeListener(v -> setUnits(parameterComboBox, unitComboBox));
grid.getColumn("parameter").setEditorField(parameterComboBox);
grid.getColumn("unit").setEditorField(unitComboBox);
Units could be updated like this. I think you need to preserve current value and set it back if you replace available items in the combobox.
private void setUnits(ComboBox parameterComboBox, ComboBox unitComboBox) {
Object currentValue = unitComboBox.getValue();
List<String> units = unitsForParameter(parameterComboBox.getValue());
unitComboBox.removeAllItems();
unitComboBox.addItems(units);
if (units.contains(currentValue)) {
unitComboBox.setValue(currentValue);
} else {
unitComboBox.setValue(null);
}
}
private List<String> unitsForParameter(Object value) {
if (value == null) {
return Collections.emptyList();
} else if (value == Parameter.Pressure) {
return asList("Pascal", "Bar");
} else if (value == Parameter.Temperature) {
return asList("Celcius", "Kelvin");
} else if (value == Parameter.Time) {
return asList("Second", "Minute");
} else {
throw new IllegalArgumentException("Unhandled value: " + value);
}
}
I have a ComboBox with a ObservableList<Item> as model. I can update, externally to it, the elements of the list and I want to update the current selected item of ComboBox if it's modified.
I tried to do:
private final ObservableList<Item> list;
private ComboBox<Item> comboBox;
....
comboBox.setItems(list);
comboBox.getSelectionModel().selectFirst();
System.out.println("selected index: " + comboBox.getSelectionModel().getSelectedIndex(); );
ListChangeListener<Item> listener = new ListChangeListener<Item>() {
#Override
public void onChanged(ListChangeListener.Change<? extends Item> c) {
while (c.next()) {
for (int i = c.getFrom(); i < c.getTo(); ++i) {
int selectedIndex = comboBox.getSelectionModel().getSelectedIndex();
System.out.println("selected index: " + selectedIndex );
if (i == selectedIndex) {
comboBox.getSelectionModel().select(selectedIndex);
}
}
}
}
};
list.addListener(listener);
I added a ChangeListener to the list, so when some element in the list is updated, I can update the selected item in the comboBox. But this doesn't work 'cause comboBox.getSelectionModel().getSelectedIndex() inside onChange returns me -1 and I don't understand why (If I print the value out of onChange, for example the first System.out, the value is correct).
Can you explain me this behavior and if there is a better way to achieve what I want?
Thanks.
my enemy movie clips are not being removed from the stage on collision/hitTestObject with the bullet movie clip, the bullets are being removed though, only the enemies that are not. Appreciate all help with this. Thanks you guys, your a brilliant community here, respect you loads.
I used the same code that made the bullet movie clips be removed for the enemies (though change changing the vars etc accordingly).
My code for Main.as and Enemy.as is here:
Main.as
package
{
import flash.display.Stage;
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
public class Main extends MovieClip
{
public var player:Player;
public var enemy:Enemy;
public var bulletList:Array = [];
public var mousePressed:Boolean = false; //keeps track of whether the mouse is currently pressed down
public var delayCounter:int = 0; //this adds delay between the shots
public var delayMax:int = 7; //change this number to shoot more or less rapidly
public var enemies:Array = [];
public function Main():void
{
player = new Player(stage, 320, 240);
stage.addChild(player);
//stage.addEventListener(MouseEvent.CLICK, shootBullet, false, 0, true); //remove this
stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler, false, 0, true);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, false, 0, true);
stage.addEventListener(Event.ENTER_FRAME, loop, false, 0, true);
for(var numBaddies=0; numBaddies<6;numBaddies++){
var enemy:Enemy = new Enemy(null);
enemy.x = numBaddies*50;
enemy.y = numBaddies*50
stage.addChild(enemy);
enemies.push(enemy);
}
}
public function loop(e:Event):void
{
if(mousePressed) // as long as the mouse is pressed...
{
delayCounter++; //increase the delayCounter by 1
if(delayCounter == delayMax) //if it reaches the max...
{
shootBullet(); //shoot a bullet
delayCounter = 0; //reset the delay counter so there is a pause between bullets
}
}
if(bulletList.length > 0)
{
for(var i:int = bulletList.length-1; i >= 0; i--)
{
bulletList[i].loop();
}
}
for(var h = 0; h<bulletList.length; ++h)
{
if(bulletList[h].hitTestObject(this)){
trace("player hit by baddie " + h);
}
}
for(var u:int=0; u<enemies.length; u++) {
Enemy(enemies[u]).moveTowards(player.x, player.y);
}
}
public function mouseDownHandler(e:MouseEvent):void //add this function
{
mousePressed = true; //set mousePressed to true
}
public function mouseUpHandler(e:MouseEvent):void //add this function
{
mousePressed = false; //reset this to false
}
public function shootBullet():void //delete the "e:MouseEvent" parameter
{
var bullet:Bullet = new Bullet(stage, player.x, player.y, player.rotation, enemies);
bullet.addEventListener(Event.REMOVED_FROM_STAGE, bulletRemoved, false, 0, true);
bulletList.push(bullet);
stage.addChild(bullet);
}
public function bulletRemoved(e:Event):void
{
e.currentTarget.removeEventListener(Event.REMOVED_FROM_STAGE, bulletRemoved);
bulletList.splice(bulletList.indexOf(e.currentTarget),1);
}
}
}
Enemy.as
package {
import flash.display.Stage;
import flash.display.MovieClip;
import flash.events.Event;
public class Enemy extends MovieClip {
public var bullets:Array;
public var stageRef:Stage;
private var enemyPositionX, enemyPositionY,xDistance,yDistance,myRotation:int;
public function Enemy(bulletList:Array) {
// constructor code
bullets = bulletList;
}
public function moveTowards(playerX:int, playerY:int){
xDistance = this.x - playerX;
yDistance = this.y - playerY;
myRotation = Math.atan2(yDistance, xDistance);
this.x -= 3 * Math.cos(myRotation);
this.y -= 3 * Math.sin(myRotation);
}
public function loop():void{
for(var i=0; i<bullets.length; ++i)
{
if(bullets[i].hitTestObject(this)){
trace("you killed enemy " + i);
removeSelf();
}
}
}
private function removeSelf():void
{
removeEventListener(Event.ENTER_FRAME, loop);
if (stageRef.contains(this))
stageRef.removeChild(this);
}
}
}
And for reference so you can see how I used the same code to remove the movie clips, I'll add the Bullet.as too:
Bullet.as
package
{
import flash.display.Stage;
import flash.display.MovieClip;
import flash.events.Event;
public class Bullet extends MovieClip
{
private var stageRef:Stage; //checks if the bullet leaves the screen borders
private var speed:Number = 10; //speed that the bullet will travel at
private var xVel:Number = 0; //current x velocity
private var yVel:Number = 0; //current y velocity
private var rotationInRadians = 0; //convenient to store our rotation in radians instead of degrees
public var allBaddies:Array;
//our constructor requires: the stage, the position of the bullet, and the direction the bullet should be facing
public function Bullet(stageRef:Stage, X:int, Y:int, rotationInDegrees:Number, enemies:Array):void
{
this.stageRef = stageRef;
this.x = X;
this.y = Y;
this.rotation = rotationInDegrees;
this.rotationInRadians = rotationInDegrees * Math.PI / 180; //convert degrees to radians, for trigonometry
allBaddies = enemies;
}
public function loop():void //we don't need to include the "e:Event" because we aren't using an EventListener
{
for(var b=0; b<allBaddies.length; ++b)
{
if(allBaddies[b].hitTestObject(this)){
trace("bullet hit baddie " + b);
removeSelf();
}
}
xVel = Math.cos(rotationInRadians) * speed; //uses the cosine to get the xVel from the speed and rotation
yVel = Math.sin(rotationInRadians) * speed; //uses the sine to get the yVel
x += xVel; //updates the position
y += yVel;
//if the bullet goes off the edge of the screen...
if(x > stageRef.stageWidth || x < 0 || y > stageRef.stageHeight || y < 0)
{
this.parent.removeChild(this); //remove the bullet
}
}
private function removeSelf():void
{
removeEventListener(Event.ENTER_FRAME, loop);
if (stageRef.contains(this))
stageRef.removeChild(this);
}
}
}
Ok there are a couple of things I would change around, alongside a couple issues I noticed.
First, your Enemy class as far as I can tell never gets the stageRef variable set. Therefore in your Enemy class removeSelf() your stageRef will be null.
You can either pass the stage in through the constructor and set it (as you do in your bullet class), or you can set it after you instantiate in your enemies loop with enemy.stageRef = stage. Or you can add and event listener to your enemy class for Event.ADDED_TO_STAGE and when that happens your enemy will have a reference to the stage inherited.
Now, the real meat of the matter. You have 2 for loops testing for collision (aside from one in your Main class too, but I'll get to that). One is in your bullet class and one is in your enemy class. This is overkill and is difficult on performance if you start adding in lots of bullets and enemies. You really only need a single for loop (like the one in your bullet class), and when it detects a collision it calls the collided objects remove method. That would look something like this in your Bullet class:
public function loop():void //we don't need to include the "e:Event" because we aren't using an EventListener
{
for(var b=0; b<allBaddies.length; ++b)
{
if(allBaddies[b].hitTestObject(this)){
trace("bullet hit baddie " + b);
removeSelf();
allBaddies[b].removeSelf();
}
}
Although, for this to work you would need to set the removeSelf() in your Enemy class access to public:
public function removeSelf():void
{
//this is removing a listener not even added Enemy...
//removeEventListener(Event.ENTER_FRAME, loop);
if (stageRef.contains(this))
stageRef.removeChild(this);
}
Then you can probably even eliminate your loop() from the Enemy class seeing as how all it does it detect collisions that will no longer be necessary.
Lastly as to why this might happened? Well it could be the bullet hitTest which is running first from your call in the main class: bulletList[i].loop(); This hitTest is being detected, then removing the bullet leaving your Enemy class without an object to hit test against. BUT! you are not calling the loop() from enemy anywhere in your main class, therefore that hit test will never run.
It's best to condense your collisions down. You are currently looping through your Bullet instances in the main class, calling their loop() (which tests for collisions), and then afterwards doing a for loop doing the collision test again. You could change this:
if(bulletList.length > 0)
{
for(var i:int = bulletList.length-1; i >= 0; i--)
{
bulletList[i].loop();
}
}
//this is no longer needed...
/*for(var h = 0; h<bulletList.length; ++h)
{
if(bulletList[h].hitTestObject(this)){
trace("player hit by baddie " + h);
}
}*/
There is no reason to test the collision in the Bullet class AND in the Main class loop(e:Event).
Hope this information helps! Good luck :)
I need to find UIElements in (rectangle/area/bounds).
MainWindow I'm doing the following:
I register the mouse down as the start position.
I regsiter the mouse up position.
Now I need to find ll (buttons, textboxes, etc) in the rectangle between start
postion and the end position.
I found in the msdn the HitTest approach but it is only for one point. I think, walking through all points in the founded
rectangle it is a performance disaster.
http://msdn.microsoft.com/en-us/library/ms752097.aspx
My code based on MVVM pattern:
private ObservableCollection<UIElementViewModel> wells;
private Point stratPoint; // Mouse down
public ICommand MouseUpRightCommand
{
get
{
if (this.mouseUpRightCommand == null)
{
this.mouseUpRightCommand = new RelayCommands(
param =>
{
if (param is MouseButtonEventArgs)
{
var e = (param as MouseButtonEventArgs);
//Set the end point
endPosition = e.GetPosition(((ItemsControl)e.Source));
// for example, here I want to find all controls(UIElements) in the
// founded rectangle of stratPoint and endPosition.
}
});
}
return this.mouseUpRightCommand;
}
}
Any other idea or a better approach?
Thanks
I would use FrameworkElement (which extends UIElement) instead of UIElement, in order to use ActualWidth and ActualHeight properties
Then create a static class which does some math MouseUtils
with those static fields
private static double _dContainerTop;
private static double _dContainerBottom;
private static double _dContainerLeft;
private static double _dContainerRight;
private static double _dCursorTop;
private static double _dCursorLeft;
private static double _dCursorRight;
private static double _dCursorBottom;
and those static methods
private static void FindValues(FrameworkElement element, Visual rootVisual)
{
var containerTopLeft = container.TransformToAncestor(rootVisual).Transform(new Point(0, 0));
_dContainerTop = containerTopLeft.Y;
_dContainerBottom = _dContainerTop + container.ActualHeight;
_dContainerLeft = containerTopLeft.X;
_dContainerRight = _dContainerLeft + container.ActualWidth;
}
and
public static bool IsElementUnderRectCursor(FrameworkElement element, Point startPoint, Point endPoint, Visual rootVisual)
{
_dCursorTop=Math.Min(startPoint.Y, endPoint.Y);
_dCursorBottom=Math.Max(startPoint.Y, endPoint.Y);
_dCursorLeft=Math.Min(startPoint.X, endPoint.X);
_dCursorRight=Math.Max(startPoint.X, endPoint.X);
FindValues(container, rootVisual);
if (_dContainerTop < _dCursorTop|| _dCursorBottom< _dContainerBottom )
{
return false;
}
if (_dContainerLeft < _dCursorLeft|| _dContainerRight < _dCursorRight)
{
return false;
}
return true;
}
Rootvisual being your window for example;
Then loop over ObservableCollection<FrameworkElement> wells and call that function IsElementUnderRectCursor.
This is inspired from:
Kinecting the Dots
Astreal thanks again for your answer. It's done. I just moved the selection code from modelView to view. The selection done only in the UI.
private void SelectWells(RectangleGeometry selectionRectangle, FrameworkElement frameworkElement)
{
var items = GetItemsControl(frameworkElement);
foreach (var item in items.Items)
{
var viusalItem = (ContentPresenter)items.ItemContainerGenerator.ContainerFromItem(item);
var wellControl = this.GetWellControl(viusalItem);
var relativePoint = wellControl.TransformToAncestor(items).Transform(new Point(0, 0));
var controlRectangle =
new RectangleGeometry(
new Rect(relativePoint.X, relativePoint.Y, wellControl.ActualWidth, wellControl.ActualHeight));
var intersectionGeometry = Geometry.Combine(
selectionRectangle, controlRectangle, GeometryCombineMode.Intersect, null);
if (intersectionGeometry.GetArea() > 0)
{
wellControl.Command.Execute(this);
}
}
}
usefull link for u:
http://www.codeproject.com/Articles/354853/WPF-Organization-Chart-Hierarchy-MVVM-Application
When an user clicks on a node in the tree we need to let the ViewModel node know that the selection has changed. We like to route the event as a command to the ViewModel