I created a TextInputDialog in JavaFX using this constructor :
public class CostumTextInputDialog extends TextInputDialog {
public CostumTextInputDialog(String headerText, String contentText, String iconURL) throws FileNotFoundException {
setHeaderText(headerText);
setContentText(contentText);
getDialogPane().setMinSize(400,260);
setGraphic(new ImageView(new Image(new FileInputStream(iconURL))));
getGraphic().maxWidth(20);
getGraphic().maxHeight(20);
showAndWait();
}
}
I called constructor here and passed the icon's URL in the parameters as defined in the constructor, but the icon is waay too big and i tried resizing it but it's not working for some reason,here's the code:
CostumTextInputDialog coverDialog = new CostumTextInputDialog(
"Creating a new table",
"Enter cover count :",
"src\\ICONS\\team.png");
what am i missing here ?
This is how is looks with MaxWidth and MaxHeight set to 20px
Related
I have a program where I can insert something in a textfield and then after pressing the enter button, it will be displayed as a label in a VBox.
My layout looks like this:
A tab with inside a borderpane with on the bottom a hbox containing a textfield and a button and at the top a scrollpane containing a vbox full of labels.
This is the code:
Tab consoleTab = new Tab("Console");
consoleTab.setClosable(false);
BorderPane consoleContent = new BorderPane();
TextField commandEntry = new TextField();
commandEntry.setPromptText("Enter command...");
Button exe = new Button("Enter");
HBox input = new HBox(5, commandEntry, exe);
VBox outputL = new VBox();
ScrollPane output = new ScrollPane();
output.setMinHeight(365);
output.setMaxHeight(365);
output.setContent(outputL);
EventHandler<ActionEvent> customEvent = e -> {
String in = commandEntry.getText();
if (in.equals("")) return;
Label inserted = new Label("> "+in);
inserted.setStyle("-fx-font-weight: bold");
outputL.getChildren().add(inserted);
commandEntry.setText("");
Command cmd = new Command(in, outputL);
cmd.execute(true);
output.setVvalue(1); // This does not work
};
commandEntry.setOnAction(customEvent);
exe.setOnAction(customEvent);
consoleContent.setTop(output);
consoleContent.setBottom(input);
consoleContent.setPadding(new Insets(5, 5, 5, 5));
consoleTab.setContent(consoleContent);
And this is the Command.java class:
public class Command {
private String command;
private VBox vbox;
public static final String NEW_FILE = "new_file";
public static final String OPEN_FILE = "open";
public static final String SAVE_FILE = "save";
public static final String LIST_FILES = "list";
public static final String HELP = "help";
public Command(String command, VBox v){
this.command = command;
this.vbox = v;
}
public void execute(boolean layout){
String[] args = this.command.split(" ");
String cmd = args[0];
String outputText = "";
switch (cmd){
case NEW_FILE:
break;
case OPEN_FILE:
outputText = "File opened";
break;
case SAVE_FILE:
break;
case LIST_FILES:
outputText = "Files listed";
break;
case HELP:
outputText = "Available commands:\nOPEN: open <file-name>\nLIST: list";
break;
default:
outputText = "Command not found, type help to get the list of available commands";
break;
}
if (layout){
makeLayout(outputText);
}
}
private void makeLayout(String outputText){
this.vbox.getChildren().add(new Label(outputText));
}
}
The problem is that when I call the setVvalue(1.0) method of the scrollpane, this is not setting the scrollbar at the bottom.
I have tried with using output.setContent(outputL) before output.setVvalue(1.0) but nothing changes.
Thanks for any help
Generate a layout pass before setting the scroll value. To generate a layout pass see:
Get the height of a node in JavaFX (generate a layout pass)
// change the content of the scroll pane
// . . .
// generate a layout pass on the scroll pane.
scrollPane.applyCss();
scrollPane.layout();
// scroll to the bottom of the scroll pane.
scrollPane.setVvalue(scrollPane.getVmax());
Why this works
When the layout pass occurs, the vValue of the scroll pane will change to keep the currently visible area displayed rather than the new area. If you then set the vValue to the maximum value, it will change from the value calculated in the layout pass to the maximum value, scrolling the pane to the bottom of the visible content.
Sample code
This is just a code snippet to demonstrate the approach, not an executable application.
I did test the approach with the example code in the original question, and it worked fine.
public void start(Stage stage) {
VBox content = new VBox();
final ScrollPane scrollPane = new ScrollPane();
scrollPane.setContent(content);
Button append = new Button("Append");
append.setOnAction(e -> appendToScrollPane(scrollPane));
VBox layout = new VBox(scrollPane, append);
stage.setScene(new Scene(layout));
stage.show();
}
public void appendToScrollPane(ScrollPane scrollPane) {
// ... actions which add content to the scroll pane ...
// generate a layout pass on the scroll pane.
scrollPane.applyCss();
scrollPane.layout();
// scroll to the bottom of the scroll pane.
scrollPane.setVvalue(scrollPane.getVmax());
}
I used grid component, and added row details..
However grid doesn't show details..
It's my code.
====
Grid grid = new Grid("Plain Grid");
grid.setDetailsGenerator(new DetailsGenerator() {
#Override
public Component getDetails(RowReference rowReference) {
// Find the bean to generate details for
final TrainingMemberVo bean = (TrainingMemberVo) rowReference.getItemId();
// A basic label with bean data
Label label = new Label("Extra data for " + bean.getMemberName());
// A button just for the sake of the example
Button button = new Button("Click me", new Button.ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
Notification.show("Button clicked for " + bean.getMemberName());
}
});
// Wrap up all the parts into a vertical layout
VerticalLayout layout = new VerticalLayout(label, button);
layout.setSpacing(true);
layout.setMargin(true);
return layout;
}
});
grid.addItemClickListener(new ItemClickListener() {
#Override
public void itemClick(ItemClickEvent event) {
if (event.isDoubleClick()) {
Object itemId = event.getItemId();
grid.setDetailsVisible(itemId, !grid.isDetailsVisible(itemId));
}
}
});
===
double clicked row, noting shows details..
Works for me without any issues. Have you checked if the event in the itemClick() is correctly fired? I. e. remove the boolean check for event.isDoubleClick() to see if it gets executed.
I am new to JavaFx and hence I cannot find a solution to solve my problem
Suppose I have following application structure :
- views
- first.fxml -> this has a button called btnSend and a textfield called txtEnter
- second.fxml -> this has a textarea called txtView
- Controller
- FirstController -> controller for First
- SecondController -> controller for second
- Modal
- AppModal -> here I have a getter and a setter method ,
as getText() and setText(String text)
- App
- Main.java -> This one used FXMLLoader to load first.fxml and second.fxml together.
What is the optimal/best way to display the text in SecondController passing it from FirstController. I mean, I enter a text in txtEnter and press the button btnSend and after pressing the button I want the text to be displayed in txtView which is using another controller.
I have read a lot about the observers pattern and JavaFX properties can be used to solve this, but unfortunately I am unable to implement a working solution.
I would be humbly thankful if you experts can help me in this. I know its not correct but can anyone please give me a working solution for the above project structure.
Thanks in advance.
Use an observable StringProperty in the model:
public class AppModel {
private final StringProperty text = new SimpleStringProperty();
public StringProperty textProperty() {
return text ;
}
public final String getText() {
return textProperty().get();
}
public final void setText(String text) {
textProperty().set(text);
}
}
Make your controllers have access to the model:
public class FirstController {
private final AppModel model ;
#FXML
private TextField textEnter ;
public FirstController(AppModel model) {
this.model = model ;
}
// action event handler for button:
#FXML
private void sendText() {
model.setText(textEnter.getText());
}
}
and
public class SecondController {
private final AppModel model ;
#FXML
private TextArea txtView ;
public SecondController(AppModel model) {
this.model = model ;
}
public void initialize() {
// update text area if text in model changes:
model.textProperty().addListener((obs, oldText, newText) ->
txtView.setText(newText));
}
}
The slightly tricky part now is that the controllers don't have a no-arg constructor, which means the default mechanism for the FXMLLoader to create them won't work. The easiest way is to set them manually. Remove both the <fx:controller> attributes from the FXML files, and then in your Main class do
AppModel model = new AppModel();
FXMLLoader firstLoader = new FXMLLoader(getClass().getResource("first.fxml"));
firstLoader.setController(new FirstController(model));
Parent firstUI = firstLoader.load();
FXMLLoader secondLoader = new FXMLLoader(getClass().getResource("second.fxml"));
secondLoader.setController(new SecondController(model));
Parent secondUI = secondLoader.load();
If you prefer to keep the <fx:controller> attributes in the FXML files, you can use a controllerFactory instead, which essentially instructs the FXMLLoader as to how to create a controller:
AppModel model = new AppModel();
Callback<Class<?>, Object> controllerFactory = type -> {
if (type == FirstController.class) {
return new FirstController(model);
} else if (type == SecondController.class) {
return new SecondController(model);
} else {
try {
return type.newInstance() ; // default behavior - invoke no-arg construtor
} catch (Exception exc) {
System.err.println("Could not create controller for "+type.getName());
throw new RuntimeException(exc);
}
}
};
FXMLLoader firstLoader = new FXMLLoader(getClass().getResource("first.fxml"));
firstLoader.setControllerFactory(controllerFactory);
Parent firstUI = firstLoader.load();
FXMLLoader secondLoader = new FXMLLoader(getClass().getResource("second.fxml"));
secondLoader.setControllerFactory(controllerFactory);
Parent secondUI = secondLoader.load();
You can make the controller factory even more flexible by using (more) reflection; basically you can implement the logic "if the controller type has a constructor taking an AppModel, call that constructor, otherwise call the no-arg constructor".
If you are creating a large application which needs to do a lot of this, then you might consider using afterburner.fx, which is a framework that essentially allows you to inject the model into the controllers using annotations.
From the examples at Xamarin.com you can build basic M.T. Dialog apps, but how do you build a real life application?
Do you:
1) Create a single DialogViewController and tree every view/RootElement from there or,
2) Create a DialogViewController for every view and use the UINavigationController and push it on as needed?
Depending on your answer, the better response is how? I've built the example task app, so I understand adding elements to a table, click it to go to the 'next' view for editing, but how to click for non-editing? How to click a button, go next view if answer is number 1?
Revised:
There is probably no one right answer, but what I've come up with seems to work for us. Number 2 from above is what was chosen, below is an example of the code as it currently exists. What we did was create a navigation controller in AppDelegate and give access to it throughout the whole application like this:
public partial class AppDelegate : UIApplicationDelegate
{
public UIWindow window { get; private set; }
//< There's a Window property/field which we chose not to bother with
public static AppDelegate Current { get; private set; }
public UINavigationController NavController { get; private set; }
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
Current = this;
window = new UIWindow (UIScreen.MainScreen.Bounds);
NavController = new UINavigationController();
// See About Controller below
DialogViewController about = new AboutController();
NavController.PushViewController(about, true);
window.RootViewController = NavController;
window.MakeKeyAndVisible ();
return true;
}
}
Then every Dialog has a structure like this:
public class AboutController : DialogViewController
{
public delegate void D(AboutController dvc);
public event D ViewLoaded = delegate { };
static About about;
public AboutController()
: base(about = new About())
{
Autorotate = true;
about.SetDialogViewController(this);
}
public override void LoadView()
{
base.LoadView();
ViewLoaded(this);
}
}
public class About : RootElement
{
static AboutModel about = AboutVM.About;
public About()
: base(about.Title)
{
string[] message = about.Text.Split(...);
Add(new Section(){
new AboutMessage(message[0]),
new About_Image(about),
new AboutMessage(message[1]),
});
}
internal void SetDialogViewController(AboutController dvc)
{
var next = new UIBarButtonItem(UIBarButtonSystemItem.Play);
dvc.NavigationItem.RightBarButtonItem = next;
dvc.ViewLoaded += new AboutController.D(dvc_ViewLoaded);
next.Clicked += new System.EventHandler(next_Clicked);
}
void next_Clicked(object sender, System.EventArgs e)
{
// Load next controller
AppDelegate.Current.NavController.PushViewController(new IssuesController(), true);
}
void dvc_ViewLoaded(AboutController dvc)
{
// Swipe location: https://gist.github.com/2884348
dvc.View.Swipe(UISwipeGestureRecognizerDirection.Left).Event +=
delegate { next_Clicked(null, null); };
}
}
Create a sub-class of elements as needed:
public class About_Image : Element, IElementSizing
{
static NSString skey = new NSString("About_Image");
AboutModel about;
UIImage image;
public About_Image(AboutModel about)
: base(string.Empty)
{
this.about = about;
FileInfo imageFile = App.LibraryFile(about.Image ?? "filler.png");
if (imageFile.Exists)
{
float size = 240;
image = UIImage.FromFile(imageFile.FullName);
var resizer = new ImageResizer(image);
resizer.Resize(size, size);
image = resizer.ModifiedImage;
}
}
public override UITableViewCell GetCell(UITableView tv)
{
var cell = tv.DequeueReusableCell(skey);
if (cell == null)
{
cell = new UITableViewCell(UITableViewCellStyle.Default, skey)
{
SelectionStyle = UITableViewCellSelectionStyle.None,
Accessory = UITableViewCellAccessory.None,
};
}
if (null != image)
{
cell.ImageView.ContentMode = UIViewContentMode.Center;
cell.ImageView.Image = image;
}
return cell;
}
public float GetHeight(UITableView tableView, NSIndexPath indexPath)
{
float height = 100;
if (null != image)
height = image.Size.Height;
return height;
}
public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath indexPath)
{
//base.Selected(dvc, tableView, path);
tableView.DeselectRow(indexPath, true);
}
}
#miquel
The current idea of a workflow is an app that starts with a jpg of the Default.png that fades into the first view, with a flow control button(s) that would move to the main app. This view, which I had working previous to M.T.D. (MonoTouch.Dialog), which is a table of text rows with an image. When each row is clicked, it moves to another view that has the row/text in more detail.
The app also supports in-app-purchasing, so if the client wishes to purchase more of the product, then switch to another view to transact the purchase(s). This part was the main reason for switching to M.T.D., as I thought M.T.D. would be perfect for it.
Lastly there would be a settings view to re-enable purchases, etc.
PS How does one know when the app is un-minimized? We would like to show the fade in image again.
I have been asking myself the same questions. I've used the Funq Dependency Injection framework and I create a new DialogViewController for each view. It's effectively the same approach I've used previously developing ASP.NET MVC applications and means I can keep the controller logic nicely separated. I subclass DialogViewController for each view which allows me to pass in to the controller any application data required for that particular controller. I'm not sure if this is the recommended approach but so far it's working for me.
I too have looked at the TweetStation application and I find it a useful reference but the associated documentation specifically says that it isn't trying to be an example of how to structure a MonoTouch application.
I use option 2 that you stated as well, it works pretty nicely as you're able to edit the toolbar options on a per-root-view basis and such.
Option 2 is more feasible, as it also gives you more control on each DialogViewController. It can also helps if you want to conditionally load the view.
I've got the following code being called in view the viewdidload method inside of my UIViewController.
Inside the appdelegate I have a UINavigationController which is instantiated with this aforementioned controller and in turn the UINavigationController is placed inside a UITabViewController which in turn is assigned as the rootviewcontroller.
Inside the controller I'm making an async web call to get the data to populate a table, if I use the loading view code to display an activity indicator I get the following warning in monotouch.
Applications are expected to have a root view controller at the end of application launch
public class LoadingView : UIAlertView
{
private UIActivityIndicatorView _activityView;
public void ShowActivity (string title)
{
Title = title;
this.Show();
// Spinner - add after Show() or we have no Bounds.
_activityView = new UIActivityIndicatorView (UIActivityIndicatorViewStyle.WhiteLarge);
_activityView.Frame = new RectangleF ((Bounds.Width / 2) - 15, Bounds.Height - 50, 30, 30);
_activityView.StartAnimating ();
AddSubview (_activityView);
}
public void Hide ()
{
DismissWithClickedButtonIndex (0, true);
}
}
Any pointers would be gratefully received.
EDIT : I'm already setting the root view controller.
window = new UIWindow (UIScreen.MainScreen.Bounds);
window.RootViewController = tabController;
Full appDelegate code :
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
// create a new window instance based on the screen size
window = new UIWindow (UIScreen.MainScreen.Bounds);
tabController = new UITabBarController();
jobsNavigationController = new UINavigationController(new JobsController());
jobsNavigationController.NavigationBar.BarStyle = UIBarStyle.Black;
jobsNavigationController.TabBarItem.Image = UIImage.FromFile("Images/briefcase.png");
jobsNavigationController.TabBarItem.Title = "Current Positions";
myAccountNavigationController = new UINavigationController(new LoginDialogViewController());
myAccountNavigationController.NavigationBar.BarStyle = UIBarStyle.Black;
myAccountNavigationController.TabBarItem.Image = UIImage.FromFile("images/man.png");
myAccountNavigationController.TabBarItem.Title = "My Account";
tabController.SetViewControllers(new UIViewController[] { jobsNavigationController,myAccountNavigationController,new SettingsDialogViewController()},false);
window.RootViewController = tabController;
// make the window visible
window.MakeKeyAndVisible ();
return true;
}
To avoid this warning (in iOS5) and keep iOS 4.x compatibility you can do the following inside your FinishedLaunching method:
if (UIDevice.CurrentDevice.CheckSystemVersion (5, 0))
window.RootViewController = navigation;
else
window.AddSubview (navigation.View);
Look here for a more complete sample.
window.AddSubview(tabcontroller.view);
Fixed the issue, odd I don't set the rootviewcontroller anymore.