I know Task has a method updateProgress, I would need to bind progressbar to task, however I cannot do that, as I do not have progressbar as an object.
My program has a TableView. Once user enters download url and clicks download new row created in the TableView. Row has some info and progressbar column. I then start a new thread - task. Where all download is being done and I need to update progress bar in that row somehow.
I tried binding SimpleDoubleProperty to the Task but it does not update progress bar...
James D solved this in Oracle JavaFX forum thread: Table cell progress indicator. I have just copied that solution into this answer.
The solution creates multiple tasks and monitors their progress via a set of progress bars in a TableView.
The original thread also includes a solution which uses ProgressIndicators in case you prefer those to ProgressBars.
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator ;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.ProgressBarTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ProgressBarTableCellTest extends Application {
#Override
public void start(Stage primaryStage) {
TableView<TestTask> table = new TableView<TestTask>();
Random rng = new Random();
for (int i = 0; i < 20; i++) {
table.getItems().add(
new TestTask(rng.nextInt(3000) + 2000, rng.nextInt(30) + 20));
}
TableColumn<TestTask, String> statusCol = new TableColumn("Status");
statusCol.setCellValueFactory(new PropertyValueFactory<TestTask, String>(
"message"));
statusCol.setPrefWidth(75);
TableColumn<TestTask, Double> progressCol = new TableColumn("Progress");
progressCol.setCellValueFactory(new PropertyValueFactory<TestTask, Double>(
"progress"));
progressCol
.setCellFactory(ProgressBarTableCell.<TestTask> forTableColumn());
table.getColumns().addAll(statusCol, progressCol);
BorderPane root = new BorderPane();
root.setCenter(table);
primaryStage.setScene(new Scene(root));
primaryStage.show();
ExecutorService executor = Executors.newFixedThreadPool(table.getItems().size(), new ThreadFactory() {
#Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
});
for (TestTask task : table.getItems()) {
executor.execute(task);
}
}
public static void main(String[] args) {
launch(args);
}
static class TestTask extends Task<Void> {
private final int waitTime; // milliseconds
private final int pauseTime; // milliseconds
public static final int NUM_ITERATIONS = 100;
TestTask(int waitTime, int pauseTime) {
this.waitTime = waitTime;
this.pauseTime = pauseTime;
}
#Override
protected Void call() throws Exception {
this.updateProgress(ProgressIndicator.INDETERMINATE_PROGRESS, 1);
this.updateMessage("Waiting...");
Thread.sleep(waitTime);
this.updateMessage("Running...");
for (int i = 0; i < NUM_ITERATIONS; i++) {
updateProgress((1.0 * i) / NUM_ITERATIONS, 1);
Thread.sleep(pauseTime);
}
this.updateMessage("Done");
this.updateProgress(1, 1);
return null;
}
}
}
Explanatory Text Based on Comment Questions
You only need to read this section if you are having difficulties understanding how the above code works and want to gain a deeper understanding of cell value and property connections.
There is no kind of binding here (at least I do not see).
The binding (or ChangeListener, which amounts to the same thing) is hidden behind the implementation of the PropertyValueFactory and the ProgressBarTableCell. Let's look at the relevant code:
TableColumn<TestTask, Double> progressCol = new TableColumn("Progress");
progressCol.setCellValueFactory(
new PropertyValueFactory<TestTask, Double>("progress")
);
progressCol.setCellFactory(
ProgressBarTableCell.<TestTask> forTableColumn()
);
The progressCol is defined to take a TestTask as the data row and extract a double value out of the test task property.
The cell value factory defines how the double value for the column is populated. It is defined based upon a PropertyValueFactory which takes the parameter "progress". This tells the property value factory to use JavaFX naming conventions and the Java reflection API to lookup relevant methods to retrieve the data from a TestTask instance. In this case it will invoke a method named progressProperty() on the TestTask instance to retrieve the ReadOnlyDoubleProperty reflecting the tasks progress.
As it states in it's documentation, the PropertyValueFactory is just short hand for the mess of code below, but the key fact is that it is returning an ObservableValue which the Table implementation can use to set the value of the cell as the cell changes.
TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<Person, String> p) {
// p.getValue() returns the Person instance for a particular TableView row
return p.getValue().firstNameProperty();
}
});
OK, so now we have a cell's value being reflected to the double value of the task's progress whenever the task makes any progress. But we still need to graphically represent that double value somehow. This is what the ProgressBarTableCell does. It is a table cell which contains a progress bar. The forTableColumn method creates a factory which produces the ProgressBarTableCells for each non-empty row in the column and sets the progress bar's progress to match the cell value which has been linked to the task's progress property by the PropertyValueFactory.
Confusing in understanding the detailed implementation . . . sure. But these high level helper factories and cells take care of a lot of the low level linkage details for you so that you don't need to code them over and over and from a plain API usage point of view it is (hopefully) simple and logical.
Also there is no properties (like SimpleStringProperty etc.) so the question would be, what if I need like two more columns with SimpleStringProperty, how do I add them to this kind of TableView?
Use the PropertyValueFactory once again. Let's image you have a string property called URL, then you can add the columns like this:
TableColumn<TestTask, Double> urlCol = new TableColumn("URL");
urlCol.setCellValueFactory(
new PropertyValueFactory<TestTask, Double>("url")
);
Note we only needed to set the cell value factory, this is because the default cell factory for the column will return a cell containing a label which directly displays the string value of the cell.
Now for the above to work correctly we need a method on TestTask which provides a url for the task, for example:
final ReadOnlyStringWrapper url = new ReadOnlyStringWrapper();
public TestTask(String url) {
this.url.set(url);
}
public ReadOnlyStringProperty urlProperty() {
return url.getReadOnlyProperty()
}
Note that the naming convention is really important here, it must be urlProperty() it can't be anything else or the PropertyValueFactory won't find the property value accessor.
Note for these purposes, a simple String value with a getUrl() would have worked just as well as a property as a PropertyValueFactory will work with a getter as well as a property method. The only advantage of using a property method is that it allows the table value data to update automatically based on property change events, which is not possible with a straight getter. But here because the url is effectively final and doesn't change for a given task, it doesn't make a difference whether a getter or property method is provided for this file from the task.
Related
I'm trying to use Nape with HaxeFlixel. Sadly, there's almost no documentation on how to use the addons.nape package and I just can't figure out why this code isn't moving the white rectangle (_test). (I left out imports for simplicity)
class PlayState extends FlxNapeState
{
var _test = new FlxNapeSprite(16, 16);
override public function create():Void
{
super.create();
_test.makeGraphic(16, 16);
_test.body.type = BodyType.KINEMATIC;
add(_test);
}
override public function update():Void
{
_test.body.velocity.x = 100;
super.update();
}
}
There are two issues with your code:
Directly initializing the _test variable leads to the FlxNapeSprite constructor call happening in the constructor of your PlayState. create() is called after the state constructor. This can cause crashes and otherwise weird behavior since Flixel does its internal cleanup between the constructor call of the new state and create() (graphics are disposed, for example, and in this case the Nape Space instance doesn't exist yet since it's created in the super.create() call).
The FlxNapeSprite constructor has a createRectangularBody argument which defaults to true and calls the function of that same name if true. Since you're not passing any asset to the constructor, it ends up creating a Shape with a width and height of 0. This leads to the following error:
Error: Cannot simulate with an invalid Polygon
Instead, you'll want to call createRectangularBody() manually after makeGraphic() to create a Shape that matches the graphic's dimensions.
The complete, working code looks like this:
package;
import flixel.addons.nape.FlxNapeSprite;
import flixel.addons.nape.FlxNapeState;
class PlayState extends FlxNapeState
{
override public function create():Void
{
super.create();
var _test = new FlxNapeSprite(16, 16);
_test.makeGraphic(16, 16);
_test.createRectangularBody();
_test.body.velocity.x = 100;
add(_test);
}
}
Regarding documentation, the FlxNape demo is a great resource to learn from.
Im currently developing a application for watching who is responsible for different Patients, however i havent been able to solve how to fill a table with different object types.
Below is my code for my TableView controller. The TableView will end up with four different object typs, all will be retrieved from a database.
I want my table to hold Patient objects, User objects (responsible) and a RelationManager object.
Below is my code, if you need more of the code, please let me know :-).
package fird.presentation;
import fird.Patient;
import fird.RelationManager;
import fird.User;
import fird.data.DAOFactory;
import fird.data.DataDAO;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
/**
* FXML Controller class
*
* #author SimonKragh
*/
public class KMAMainFrameOverviewController implements Initializable {
#FXML
private TextField txtCPRKMAMainFrame;
#FXML
private TableColumn<Patient, String> TableColumnCPR;
#FXML
private TableColumn<Patient, String> TableColumnFirstname;
#FXML
private TableColumn<Patient, String> TableColumnSurname;
#FXML
private TableColumn<User, String> TableColumnResponsible;
#FXML
private TableColumn<RelationManager, String> TableColumnLastEdited;
#FXML
private TableView<RelationManager> tblPatients;
#FXML
private Button btnShowHistory;
#FXML
private TableColumn<?, ?> TableColumnDepartment;
/**
* Initializes the controller clas #FXML private Button btnShowHistory;
*
* #FXML private TableColumn<?, ?> TableColumnDepartment; s.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
// Start of logic for the KMAMainFrameOverviewController
DataDAO dao = DAOFactory.getDataDao();
TableColumnCPR.setCellValueFactory(new PropertyValueFactory<Patient, String>("CPR"));
TableColumnFirstname.setCellValueFactory(new PropertyValueFactory<Patient, String>("Firstname"));
TableColumnSurname.setCellValueFactory(new PropertyValueFactory<Patient, String>("Surname"));
TableColumnResponsible.setCellValueFactory(new PropertyValueFactory<User, String>("Responsible"));
TableColumnLastEdited.setCellValueFactory(new PropertyValueFactory<RelationManager, String>("Last Edited"));
ObservableList<RelationManager> relationData = FXCollections.observableArrayList(dao.getAllActiveRelations());
tblPatients.setItems(relationData);
tblPatients.getColumns().addAll(TableColumnCPR, TableColumnFirstname, TableColumnSurname, TableColumnResponsible, TableColumnLastEdited);
System.out.println(tblPatients.getItems().toString());
}
}
relationData is a RelationManager object returned. This object contains a User object, a Patient object and a Responsible object.
Best,
Simon.
The exact details of how you do this depend on your requirements: for example, for a given RelationManager object, do the User, Patient, or Responsible objects associated with it ever change? Do you need the table to be editable?
But the basic idea is that each row in the table represents some RelationManager, so the table type is TableView<RelationManager>. Each column displays a value of some type (call it S), so each column is of type TableColumn<RelationManager, S>, where S might vary from one column to the next.
The cell value factory is an object that specifies how to get from the RelationManager object to an observable value of type S. The exact way you do this depends on how your model classes are set up.
If the individual objects associated with a given RelationManager never change (e.g. the Patient for a given RelationManager is always the same), then it's pretty straightforward. Assuming you have the usual setup for Patient:
public class Patient {
private StringProperty firstName = new SimpleStringProperty(...);
public StringProperty firstNameProperty() {
return firstName ;
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
// etc etc
}
then you can just do
TableColumn<RelationManager, String> firstNameColumn = new TableColumn<>("First Name");
firstNameColumn.setCellValueFactory(new Callback<CellDataFeatures<RelationManager,String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<RelationManager, String> data) {
return data.getValue() // the RelationManager
.getPatient().firstNameProperty();
}
});
If you are not using JavaFX properties, you can use the same fallback that the PropertyValueFactory uses, i.e.:
TableColumn<RelationManager, String> firstNameColumn = new TableColumn<>("First Name");
firstNameColumn.setCellValueFactory(new Callback<CellDataFeatures<RelationManager,String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<RelationManager, String> data) {
return new ReadOnlyStringWrapper(data.getValue().getPatient().getFirstName());
}
});
but note that this won't update if you change the name of the patient externally to the table.
However, none of this will work if the patient object associated with the relation manager is changed (the cell will still be observing the wrong firstNameProperty()). In that case you need an observable value that changes when either the "intermediate" patient property or the firstNameProperty change. JavaFX has a Bindings API with some select(...) methods that can do this: unfortunately in JavaFX 8 they spew out enormous amounts of warnings to the console if any of the objects along the way are null, which they will be in a TableView context. In this case I would recommend looking at the EasyBind framework, which will allow you to do something like
firstNameColumn.setCellValueFactory( data ->
EasyBind.select(data.getValue().patientProperty())
.selectObject(Patient::firstNameProperty));
(EasyBind requires JavaFX 8, so you if you get to use it, you also get to use lambda expressions and method references :).)
In either case, if you want the table to be editable, there's a little extra work to do for the editable cells in terms of wiring editing commits back to the appropriate call to set a property.
I'm wondering if I'm doing this the correct way - this method works, but feels somewhat 'dirty'. Essentially, a button in an MvxTableViewCell changes a parameter of the bound object, but the cell does not update to reflect the change until it's scrolled out of view and back into view (ie the cell is 'redrawn'). All the examples here are simplified, but you get the idea..
Firstly, my object:
public class Expense
{
public decimal Amount { get; set; }
public bool Selected { get; set; }
public Command FlipSelected
{
get { return new MvxCommand(()=> this.Selected = !this.Selected); }
}
}
Secondly, my cell (in the constructor) contains:
this.DelayBind(() =>
{
var set = this.CreateBindingSet<HistoryCell, Expense>();
set.Bind(this.TitleText).To(x => x.Amount);
set.Bind(this.SelectButton).To(x=> x.FlipSelected);
set.Bind(this.SelectButton).For(x => x.BackgroundColor).To(x => x.Selected).WithConversion(new ButtonConverter(), null);
set.Apply();
});
And i have a valueconverter that returns the background colour of the button:
class ButtonConverter : MvxValueConverter<bool, UIColor>
{
UIColor selectedColour = UIColor.FromRGB(128, 128, 128);
UIColor unSelectedColour = UIColor.GroupTableViewBackgroundColor;
protected override UIColor Convert(bool value, Type targetType, object parameter, CultureInfo culture)
{
return value ? selectedColour : unSelectedColour;
}
protected override bool ConvertBack(UIColor value, Type targetType, object parameter, CultureInfo culture)
{
return value == selectedColour;
}
}
Right, so what happens is, if i click the button in the cell, it runs the command that flips the bool value Selected, which in turn binds back to the background colour of the cell via the ButtonConverter value converter.
The problem I'm having is that the cell doesn't update straight away - only when I scroll out of view of that cell and back into view (ie the cell is redrawn). So i thought I'd just cause the cell to become 'dirty':
this.SelectButton.TouchUpInside += (o, e) =>
{
this.SetNeedsDisplay();
};
But this doesn't work. What does work is putting additional code inside the TouchUpInside event that manually changes the background colour. But I'm assuming this isn't the correct way of doing it.
Do I need to trigger RaisePropertyChanged when I change the value of Selected in the Expense object? How can I do that when it's just an object?
Really hoping Stuart can help out on this one ;)
I think your analysis is correct - the UI isn't updating live because there are no change messages from your Expense objects.
To provide 'traditional' change notifications in your view model objects, you need to make sure each one supports INotifyPropertyChanged. This small interface is easy to implement yourself if you want to - or you can modify your Expense to inherit the built-in MvxNotifyPropertyChanged helper class if you prefer - then RaisePropertyChanged would be available.
As one other alternative, you can also implement the new 'Rio' field based binding if you prefer. For an intro to this, see N=36 in http://mvvmcross.blogspot.com
This has baffled me for a while now and I cannot seem to get the grasp of it. I'm using Cell Value Factory to populate a simple one column table and it does not populate in the table.
It does and I click the rows that are populated but I do not see any values in them- in this case String values. [I just edited this to make it clearer]
I have a different project under which it works under the same kind of data model. What am I doing wrong?
Here's the code. The commented code at the end seems to work though. I've checked to see if the usual mistakes- creating a new column instance or a new tableview instance, are there. Nothing. Please help!
//Simple Data Model
Stock.java
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getstockTicker() {
return stockTicker.get();
}
public void setstockTicker(String stockticker) {
stockTicker.set(stockticker);
}
}
//Controller class
MainGuiController.java
private ObservableList<Stock> data;
#FXML
private TableView<Stock> stockTableView;// = new TableView<>(data);
#FXML
private TableColumn<Stock, String> tickerCol;
private void setTickersToCol() {
try {
Statement stmt = conn.createStatement();//conn is defined and works
ResultSet rsltset = stmt.executeQuery("SELECT ticker FROM tickerlist order by ticker");
data = FXCollections.observableArrayList();
Stock stockInstance;
while (rsltset.next()) {
stockInstance = new Stock(rsltset.getString(1).toUpperCase());
data.add(stockInstance);
}
} catch (SQLException ex) {
Logger.getLogger(WriteToFile.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Connection Failed! Check output console");
}
tickerCol.setCellValueFactory(new PropertyValueFactory<Stock,String>("stockTicker"));
stockTableView.setItems(data);
}
/*THIS, ON THE OTHER HAND, WORKS*/
/*Callback<CellDataFeatures<Stock, String>, ObservableValue<String>> cellDataFeat =
new Callback<CellDataFeatures<Stock, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<Stock, String> p) {
return new SimpleStringProperty(p.getValue().getstockTicker());
}
};*/
Suggested solution (use a Lambda, not a PropertyValueFactory)
Instead of:
aColumn.setCellValueFactory(new PropertyValueFactory<Appointment,LocalDate>("date"));
Write:
aColumn.setCellValueFactory(cellData -> cellData.getValue().dateProperty());
For more information, see this answer:
Java: setCellValuefactory; Lambda vs. PropertyValueFactory; advantages/disadvantages
Solution using PropertyValueFactory
The lambda solution outlined above is preferred, but if you wish to use PropertyValueFactory, this alternate solution provides information on that.
How to Fix It
The case of your getter and setter methods are wrong.
getstockTicker should be getStockTicker
setstockTicker should be setStockTicker
Some Background Information
Your PropertyValueFactory remains the same with:
new PropertyValueFactory<Stock,String>("stockTicker")
The naming convention will seem more obvious when you also add a property accessor to your Stock class:
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getStockTicker() {
return stockTicker.get();
}
public void setStockTicker(String stockticker) {
stockTicker.set(stockticker);
}
public StringProperty stockTickerProperty() {
return stockTicker;
}
}
The PropertyValueFactory uses reflection to find the relevant accessors (these should be public). First, it will try to use the stockTickerProperty accessor and, if that is not present fall back to getters and setters. Providing a property accessor is recommended as then you will automatically enable your table to observe the property in the underlying model, dynamically updating its data as the underlying model changes.
put the Getter and Setter method in you data class for all the elements.
I am trying to figure out how to:
1. use IB, in Xcode 4+ to visually create a custom subclass of UITableViewCell to use in MT.
How to use that custom class as an element in MT.Dialog.
I have searched extensively and haven't found any example or been able to solve it.
Here is the process I have been trying:
Step 1 seems easy enough now that I have found a good tutorial: http://www.arcticmill.com/2012/05/uitableview-with-custom-uitableviewcell.html
Step 2 seems to be where I am stuck. Once I have the new class, with a few labels dropped onto it in this case:
public partial class CustomListCell : UITableViewCell {
public CustomListCell () :base(UITableViewCellStyle.Default,"CellID") {
}
public void UpDateData(string lbl1, string lbl2, string lbl3) {
this.lblLabel1.Text = lbl1;
this.lblLabel2.Text = lbl2;
this.lblLabel3.Text = lbl3;
}
}
I cannot figure out how to turn it into something I can use in MT.Dialog. I have tried :
public partial class CustomListCell :Element
but the label controls don't seem to every be created.No matter where I put a call to UpdateData they are all null, hence a null reference exception, even if the constructor has executed just fine. I've also tried making it an OwnerDrawnElement, but ran into a couple of problems with that.
Is this possible? Is there a recommended pattern?
I think the sample you are looking for is the OwnerDrawnCell: https://github.com/migueldeicaza/MonoTouch.Dialog/blob/master/MonoTouch.Dialog/Elements/OwnerDrawnElement.cs
See how it overrides the GetCell() method to provide a custom cell:
public override UITableViewCell GetCell (UITableView tv)
{
OwnerDrawnCell cell = tv.DequeueReusableCell(this.CellReuseIdentifier) as OwnerDrawnCell;
if (cell == null)
{
cell = new OwnerDrawnCell(this, this.Style, this.CellReuseIdentifier);
}
else
{
cell.Element = this;
}
cell.Update();
return cell;
}
You just need to do the same thing - except you need to replace OwnerDrawnCell with the XIB-loaded cell.
I've also done a blog post on how I load cells from XIBs using the new iOS6 variant of the DequeueReusableCell API - see http://slodge.blogspot.co.uk/2013/01/uitableviewcell-using-xib-editor.html