enter image description hereI made a GUI on JavaFX that's for creating timetables. when I open the app I can add plans (Buttons) to day columns (VBox). ofc the changes aren't saved after I close the app: the next time I open it the table is empty.
my question is how can I make it save nodes that the user creates so the next time i open the app they're there?
this is the exact part where the nodes get added for what it's worth:
void ask_add_plan(ActionEvent event)
{
Button b = (Button) event.getSource();
VBox v = (VBox) b.getParent();
AnchorPane pop_up = new AnchorPane(); //this pop up is to specify things about the plan
//but i removed unnecessary code for simplicity
VBox pop_up_v = new VBox();
Button add = new Button("Add");
add.setOnAction(e->{
Button plan = new Button(what_to_do.getText());
v.getChildren().add(plan);
container.getChildren().remove(pop_up); //container is the anchor pane everything's in
});
pop_up_v.getChildren().add(add);
pop_up.getChildren().add(pop_up_v);
container.getChildren().add(pop_up); //container is the anchor pane everything's in
}
The JavaFX nodes are just the presentation of your data. They are not meant to be saved. Store the actual data itself in a private field in your class.
In your Application.stop method, write the data to a file.
In your Application.start method, read that file and use it to rebuild JavaFX nodes.
private static final Path PLANS_FILE =
Path.of(System.getProperty("user.home"), "plans.txt");
private final List<String> plans = new ArrayList<>();
void ask_add_plan(ActionEvent event) {
// ...
add.setOnAction(e -> {
String planText = what_to_do.getText();
plans.add(planText);
Button plan = new Button(planText);
v.getChildren().add(plan);
container.getChildren().remove(pop_up);
});
}
#Override
public void start(Stage primaryStage)
throws Exception {
// ...
if (Files.exists(PLANS_FILE)) {
plans.addAll(Files.readAllLines(PLANS_FILE));
// Add UI elements for each stored plan.
for (String planText : plans) {
Button planButton = new Button(planText);
v.getChildren().add(planButton);
container.getChildren().remove(pop_up);
}
}
}
#Override
public void stop()
throws IOException {
Files.write(PLANS_FILE, plans);
}
The above is just a simplified example. The file doesn’t have to store just strings.
I get the impression that the data created in the application is more complex than just plan strings. For complex data, XML may be more a suitable file format. Or you can use Java serialization. Or, you can invent your own file format.
You also don’t have to read from and write to a file. You can use a database, if you’re comfortable with database programming. Or you can use Preferences (though Preferences are not well suited to storing lists of complex data).
You should use MVP (model-view-presenter) pattern. Saving data in UI layer is not a good practice. Create a model with data and then serialize it.
Related
I am using JavaFx, and have Tabs with WebView objects on them. I am trying to have my tab title's text to read the web page's title like in most web browsers. When I use the "getTitle" method I get an empty title, Which I assume is because the page hasn't loaded yet. All the research I've done gives me an Android Solution and I'm looking for something that works with a desktop application. Here's what I have.
public class WebsiteTab extends Tab {
final static String DEFAULT_SITE = "https://google.com";
VBox browserBox;
WebView webView;
public WebsiteTab() {
super("Site One");
webView = new WebView();
webView.setPrefHeight(5000);
goToSite(DEFAULT_SITE);
browserBox = new VBox(10,webView);
VBox.setVgrow(browserBox, Priority.ALWAYS);
setContent(browserBox);
}
public void goToSite(final String site) {
webView.getEngine().load(site);
setText(webView.getEngine().getTitle());
}
}
Any help would be appreciated.
You can bind the tab's text property to the web engine's title property.
public class WebsiteTab extends Tab {
final static String DEFAULT_SITE = "https://google.com";
VBox browserBox;
WebView webView;
public WebsiteTab() {
super("Site One");
webView = new WebView();
webView.setPrefHeight(5000);
textProperty().bind(webView.getEngine().titleProperty()); // bind the properties
goToSite(DEFAULT_SITE);
browserBox = new VBox(10,webView);
VBox.setVgrow(browserBox, Priority.ALWAYS);
setContent(browserBox);
}
public void goToSite(final String site) {
webView.getEngine().load(site);
}
}
This will cause the text property to always have the same value as the title property. In other words, when the title property's value changes the text property will be automatically updated. Notice I bind the properties in the constructor as you only need to create the binding once. Also note that while bound you can no longer manually set the text property; attempting to do so will cause an exception to be thrown. For more information, see Using JavaFX Properties and Binding.
I am working a calculator, and I want have a log stage opened on the side, now my problem is that I need to update the stage, I figured the easiest way would be to just check if the stage is showing, and if it is then hide it, that just doesn't seem to be working, here is my code:
public void start(Stage logStage) {
boolean open = logStage.isShowing();
System.out.println(open);
if(open == true){
logStage.hide();
System.out.println("Should be hidden now!");
}
logStage.setTitle("Log");
GridPane grid = new GridPane();
grid.setAlignment(Pos.TOP_CENTER);
grid.setHgap(5);
grid.setVgap(5);
TextArea logText = new TextArea();
for (String log : Logger.fraLog()) {
logText.setText(logText.getText() + log);
}
grid.add(logText, 0, 0, 10, 10);
Scene scene = new Scene(grid);
logStage.setHeight(210);
logStage.setWidth(300);
logStage.setX(1135);
logStage.setY(350);
logStage.setScene(scene);
logStage.setResizable(false);
logStage.setAlwaysOnTop(true);
logStage.show();
}
And after running this method 3 times my output is:
false
false
false
The way I call the method is:
Logger logger = new Logger();
logger.start(new Stage());
Any help is appreciated
A new Stage is hidden until you show it. Since you're creating new Stages every time, none of them is shown when you read the showing property.
Furthermore since you're calling show() for the Stage anyways, you undo any effects of the hide() call.
Probably you should reuse the same Stage/scene graph. Furthermore it's unclear what effect you expect from the hide() call since you're showing the window again; simply replacing the content without hiding the Stage would have the same effect.
If you need to update the TextArea from another class, then pass a reference to it, e.g. in the constructor of the other class. This might look like:
a) You class:
public class MyClass {
private TextArea textarea;
public MyClass(TextArea t){
this.textarea = t;
}
public void someMethodThatNeedsToLog(){
this.textarea.appendText("the next log message ...");
}
}
b) Your initialization code:
public void start(Stage logStage) {
// ...
TextArea logText = new TextArea();
MyClass instance = new MyClass(logText);
// ...
logStage.show();
Hope that helps ...
BTW: This is not a matter of JavaFX or UI Programming. You might want to have a look on principles of object oriented programming like "inversion of control", "dependency injection/constructor injection".
We want to achieve an RCP application which may have multiple windows (MWindow)
for distinct data. The Windows must be independent (unlike the Eclipse IDE new
window menu entry), but it must be possible to copy & paste, drag & drop things from
one window into another one. Imagine an application like Word where you can
have multiple documents open. We tried various approaches, but it is quiet
difficult to find out the right e4 way:
1. Creating a new E4Application for each window
Our first approach was to create and run a complete new E4Application for each
new window. But this sounds not to be the right e4 way. Also it is buggy: Key
bindings does not work correct and also the LifecycleManager is called for each new
application and therefor for each new window, which should not be.
E4Application application = new E4Application();
BundleContext context = InternalPlatform.getDefault().getBundleContext();
ServiceReference<?> appContextService = context.getServiceReference(IApplicationContext.class);
IApplicationContext iac = (IApplicationContext) context.getService(appContextService);
IWorkbench workbench = application.createE4Workbench(iac, display);
final E4Workbench implementation = (E4Workbench) workbench;
implementation.createAndRunUI(workbench.getApplication());
This seems not the right approach to do it.
2. The Eclipse IDE approach
In the Eclipse IDE you can go to the menu and click Window -> New Window which
will open a complete new top level window. But it is synchronized: Open the
same text file in both windows and editing it in the first one will alter it in
the other one too. Albeit we tried that approach by simply copy and pasting it
from org.eclipse.ui.actions.OpenInNewWindowAction#run():
// Does not work because we do not have the RCP3 workbench in RCP4.
final IWorkbench workbench = PlatformUI.getWorkbench();
final IWorkbenchWindow workbenchWindow = workbench.getActiveWorkbenchWindow();
final IWorkbenchPage activePage = workbenchWindow.getActivePage();
final String perspectiveId;
if (activePage != null && activePage.getPerspective() != null) {
perspectiveId = activePage.getPerspective().getId();
} else {
perspectiveId = workbenchWindow.getWorkbench().getPerspectiveRegistry().getDefaultPerspective();
}
workbenchWindow.getWorkbench().openWorkbenchWindow(perspectiveId, null);
It looks like that the Eclipse IDE uses the RCP3 compatibility layer. We didn't
found a way to obtain the IWorkbench object. Neither by
PlatformUI#getWorkbench(), nor via the application context, nor the bundle
context.
3. Clone the main window
We stumbled upon Opening multiple instances of an MTrimmedWindow complete with perspectives etc
n-mtrimmedwindow-complete-with-perspectives-etc and did a lot of trial and
error and came up with this muddy code:
class ElementCloningBasedCreator {
EModelService models = ...; // injected
MApplication app = ...; // injected
public void openNewWindow() {
MWindow originWindow = (MWindow) models.find("the.main.window.id", app);
MWindow newWindow = (MWindow) models.cloneElement(originWindow, null);
MPerspectiveStack newPerspectiveStack =
(MPerspectiveStack) models.find(the.main.perspective.stack.id, newWindow);
newPerspectiveStack.setParent((MElementContainer) newWindow);
addTo(app, newWindow);
// Clone the shared elements. If we don't do that the rendering somewhere
// deep in the rabbit hole throws assertion erros because the recurisve
// finding of an element fails because the search root is null.
for (final MUIElement originSharedElement : originWindow.getSharedElements()) {
final MUIElement clonedSharedElement = models.cloneElement(originSharedElement, null);
clonedSharedElement.setParent((MElementContainer) newWindow);
newWindow.getSharedElements().add(clonedSharedElement);
}
cloneSnippets(app, originWindow, newPerspectiveStack, newWindow);
newWindow.setContext(createContextForNewWindow(originWindow, newWindow));
newWindow.setToBeRendered(true);
newWindow.setVisible(true);
newWindow.setOnTop(true);
models.bringToTop(newWindow);
}
#SuppressWarnings({ "rawtypes", "unchecked" })
private void addTo(MElementContainer target, MUIElement child) {
child.setParent(target);
target.getChildren().add(child);
}
/**
* Clone each snippet that is a perspective and add the cloned perspective
* into the main PerspectiveStack.
*/
private void cloneSnippets(MApplication app, MWindow originWindow,
MPerspectiveStack newPerspectiveStack, MWindow newWindow) {
boolean isFirstSnippet = true;
for (MUIElement snippet : app.getSnippets()) {
if (ignoreSnippet(snippet)) {
continue;
}
String snipetId = snippet.getElementId();
MPerspective clonedPerspective =
(MPerspective) models.cloneSnippet(app, snipetId, originWindow);
findPlaceholdersAndCloneReferencedParts(clonedPerspective, newWindow);
addTo(newPerspectiveStack, clonedPerspective);
if (isFirstSnippet) {
newPerspectiveStack.setSelectedElement(clonedPerspective);
isFirstSnippet = false;
}
}
}
private boolean ignoreSnippet(MUIElement snippet) {
return !(snippet instanceof MPerspective);
}
private void findPlaceholdersAndCloneReferencedParts(MPerspective clonedPerspective, MWindow newWindow) {
List<MPlaceholder> placeholders =
models.findElements(clonedPerspective, null, MPlaceholder.class, null);
for (MPlaceholder placeholder : placeholders) {
MUIElement reference = placeholder.getRef();
if (reference != null) {
placeholder.setRef(models.cloneElement(placeholder.getRef(), null));
placeholder.getRef().setParent((MElementContainer) newWindow);
}
}
}
}
This code does not really work and we really need some hints/advices how to do
it right, because of the lack of official documentation. The questions open are:
Do we need to clone the shared objects and if not how do we prevent the
errors during rendering)?
We only saw code where the cloned elements are
added to the parent via getChildren().add(), but we found out that the
children din't get the parent automatically and it is null though. Is it the
right pattern to add the parent to the child too?
We have the deep feeling
that we are doing it not right. It looks way too complicated what we do here. Is
there a simpler/better approach?
You can use the EModelService cloneSnippet method to do this.
Design your MTrimmedWindow (or whatever type of window you want) in the Snippets section of the Application.e4xmi. Be sure that the To Be Rendered and Visible flags are checked. You may need to set the width and height bounds (and you may want to set the x and y position as well).
Your command handler to create the new window would simply be:
#Execute
public void execute(EModelService modelService, MApplication app)
{
MTrimmedWindow newWin = (MTrimmedWindow)modelService.cloneSnippet(app, "id of the snippet", null);
app.getChildren().add(newWin);
}
I'm trying to write a simple application to display chart data. I want to display some data as soon as the user loads the page, so I'm getting data & drawing tables inside of the Runnable as described in the gwt-visualization Getting Started.
Things seem to work alright, except charts tend to get loaded more than once. Below is my onModuleLoad().
private final StatisticsServiceAsync statisticsService = GWT.create(StatisticsService.class);
GWTBeanFactory factory = GWT.create(GWTBeanFactory.class);
DataTable locationData;
AnnotatedTimeLine atl;
GeoMap usMap;
TextBox storeField;
Button log10Button;
DateRange durationChartRange;
String eusrJson = null;
Button b;
HTML last1000Html;
public void onModuleLoad() {
storeField = new TextBox();
storeField.setText("Enter a store");
storeField.addKeyDownHandler(new MyKeyHandler());
b = new Button("Get Stats!");
log10Button = new Button("Show Log10 Scale");
log10Button.addClickHandler(new Log10ClickHandler());
b.addClickHandler(new MyClickHandler());
last1000Html = new HTML();
getLast1000Avg();
Runnable onLoadCallback = new Runnable() {
public void run() {
storeDurationData = DataTable.create();
storeDurationDataLog10 = DataTable.create();
RootPanel.get("storeDurationDiv").add(storeField);
RootPanel.get("storeDurationDiv").add(b);
RootPanel.get("storeDurationDiv").add(log10Button);
RootPanel.get("storeDurationDiv").add(last1000Html);
log10Button.setVisible(false);
// Get initial Data
getAvgByRegion();
getLast1000Avg();
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
#Override
public boolean execute() {
getLast1000Avg();
return true;
}
}, 5000);
}
};
// Load the visualization api, passing the onLoadCallback to be called
// when loading is done.
VisualizationUtils.loadVisualizationApi(onLoadCallback, AnnotatedTimeLine.PACKAGE);
VisualizationUtils.loadVisualizationApi(onLoadCallback, GeoMap.PACKAGE);
}
All of the "simple" elements seem to get populated correctly, as the Button, HTML, and TextBox all get placed appropriately (which used to be inside of run, they're where they are now as a result of debugging previous errors). However, the GeoMap gets placed twice, and looking at the logging you can tell that the Runnable's run is being executed at least twice, which seems reasonable, but I don't know how to keep it from adding twice.
I'm probably screwing up something with the Async stuff, but I'm new and confused. Below is my getAvgByRegion() method:
private void getAvgByRegion() {
statisticsService.getEusrForRegion(new AsyncCallback<String>() {
#Override
public void onFailure(Throwable caught) {
System.out.println(":(");
}
#Override
public void onSuccess(String result) {
createLocTable();
DataTable dt = parseEusrLocations(result);
usMap = new GeoMap(dt, createGeoMapOptions());
usMap.setSize("800px", "600px");
RootPanel.get("storeDurationDiv").add(usMap);
}
});
}
Any advice on how best to work with GWT is welcome.
So, you call VisualizationUtils.loadVisualizationApi twice, so the onLoadCallback will be run twice (I don't know GWT Google Apis, this is a supposition).
onLoadCallback calls getAvgByRegion, so that one will get called twice too; and it gets data and in the callback creates a new GeoMap and adds it to the RootPanel.get("storeDurationDiv"), so you get two GeoMaps on the screen.
The other widgets(storeField, etc.) are created only once, so adding them repeatedly is not a problem (except performance-wise), as they'll first be removed from their current parent before being added to the new one (which in this case is the same)
I have my form Welcome on this form i have two radio buttons-Verification and enrollment and a OK button .when user select one of radio buttons and press OK then a form will show but i am not able to do that. Please help.
this is my Statemachine class code:
package userclasses;
import generated.StateMachineBase;
import com.sun.lwuit.*;
import com.sun.lwuit.events.*;
import com.sun.lwuit.RadioButton;
import com.sun.lwuit.Form;
import com.sun.lwuit.util.Resources;
public class StateMachine extends StateMachineBase implements ActionListener {
Resources resources;
RadioButton Verification = new RadioButton("Verification");
RadioButton Enrollment = new RadioButton("Enrollment");
StateMachineBase cl = new StateMachineBase() { };
com.sun.lwuit.ButtonGroup bg=new ButtonGroup();
Form fo, f;
public StateMachine(String resFile) {
super(resFile);
// do not modify, write code in initVars and initialize class members there,
// the constructor might be invoked too late due to race conditions that might occur
}
/**
* this method should be used to initialize variables instead of
* the constructor/class scope to avoid race conditions
*/
StateMachine()
{
try {
resources = Resources.open("/NEW AADHAR.res");
}
catch(java.io.IOException err) {
err.printStackTrace();
}
cl.setHomeForm("Welcome");
fo = (Form)cl.startApp(resources,null,true);
f = cl.findWelcome(fo);
//fo.addCommandListener(this);
Verification = cl.findRadioButton1(f);
Enrollment = cl.findRadioButton(f);
f.addComponent(Verification);
f.addComponent(Enrollment);
//fo.addComponent(bg,null);
bg.add(Enrollment);
bg.add(Verification);
Verification.addActionListener(this);
Enrollment.addActionListener(this);
}
protected void initVars() { }
protected void onWelcome_OKAction(Component c, ActionEvent event) { }
public void actionPerformed(ActionEvent ae) {
throw new UnsupportedOperationException("Not supported yet.");
}
protected boolean onWelcomeEXIT() {
// If the resource file changes the names of components this call will break notifying you that you should fix the code
boolean val = super.onWelcomeEXIT();
return val;
}
protected void onWelcome_ButtonAction(Component c, ActionEvent event) {
// If the resource file changes the names of components this call will break notifying you that you should fix the code
super.onWelcome_RadioButton1Action(c, event);
super.onWelcome_RadioButtonAction(c, event);
if(Verification.hasFocus()) {
showForm("Login",null);
}
else if(Enrollment.hasFocus()) {
showForm("Authentication",null);
}
else {
Dialog.show("INFORMATION","Please select option","OK","CANCEL");
}
}
|
When you generate a netbeans project from the GUI builder the src folder will now contain the res file you need to work with. Whenever you modify the GUI code that StateMachineBase will be regenerated so you can just rename the components in the GUI builder (you can do this by clicking on the tree node and pressing F2 or by selecting the name attribute in the properties table).
The properties table allows you to assign an event for every component that supports it (e.g. radio button action events) which will generate the appropriate callback method in the StateMachine class (write your code only in the StateMachine class).
Radio buttons can be associated with one group by giving them the same group name.
The easiest way to do it is to use Resource Editor. Simply run it from LWUIT/util directory.
To create project using this tool follow each step from this video: http://www.youtube.com/watch?v=HOfb8qiySd8. Be sure to watch it to the end.
It will create 4 Netbeans projects (ProjectName, ProjectName_Desktop, ProjectName_MIDP, ProjectName_RIM). Fix depedencies (most important for ProjectName and _MIDP one) and you can start coding.
File StateMachineBase.java will be located in 'generated' package, which means that it will be regenerated every time you change something in Resource Editor.
Implement everything in StateMachine class ('userclasses' package), but don't create new methods there, use Resource Editor to create them for you: Resource Editor -> GUI Builder (tab on left side) -> Select component -> Events (tab on the right).
Now, if you want to do something for example, you want to change TextField value, you will write something like this:
protected boolean onUstawieniaKontoZapisz() {
// If the resource file changes the names of components this call will break notifying you that you should fix the code //this comment was generated
boolean val = super.onUstawieniaKontoZapisz(); //generated
Form current = Display.getInstance().getCurrent();
TextField login = findUstawieniaKontoLoginTextField(current); //TextField name in Editor is: 'UstawieniaKontoLoginTextField' - a bit long I know, but it's unique
TextField password = findUstawieniaKontoHasloTextField(current); //same here, 'UstawieniaKontoHasloTextField' is second's TextField name
Configuration.setEmail(login.getText()); //Configuration class is used to store preferences
Configuration.setPassword(password.getText());
return val; //generated
}
You can find all 'find*' methods inside StateMachineBase class. There is one for each Component you have added using Resource Editor (GUI Builder tab).
For grouping radio buttons into groups use Resource Editor too, select each radio button and on Properties tab find 'Group' property. Set it to the same word on every radio button you want to have in the same group.