I have a GWT bootstrap 3 button as a ButtonCell created with IconType and ButtonType:
public abstract class ButtonColumn<T> extends Column<T, String> {
public ButtonColumn(IconType iconType, ButtonType buttonType) {
this(new ButtonCell(buttonType, iconType));
}
}
So when I create the button, I do
new ButtonColumn<Object>(IconType.PLAY, ButtonType.SUCCESS) {
#Override
public void onClick(Object obj) {
doStuff(obj);
}
};
I want to change my button IconType onClick. Is it possible to achieve it?
And can I create a custom IconType extending the GWT IconType Enum? I wanted to put an animated icon (like a loading icon).
Well, you can not change the button's icon in a row, especially when you create the whole column with an icon already specified. But you can redraw() a row and this could be a way to achieve what you want.
I use AbstractCell to render a button and onBrowserEvent:
first create an AbstractCell with ClickEvent in consumedEvents parameter
in the render() method render a button based on the clicked state
in the onBrowserEvent() method change the clicked state and re-render the row
The clicked state is best to be kept in the table's underlying data type so it is available for each row.
Here is a complete working example code:
final CellTable<TableType> table = new CellTable<TableType>();
AbstractCell<TableType> buttonCell = new AbstractCell<ButtonCellTest.TableType>(ClickEvent.getType().getName()) {
#Override
public void render(Context context, TableType value, SafeHtmlBuilder sb) {
Button button = new Button();
button.setType(ButtonType.SUCCESS);
button.setSize(ButtonSize.SMALL);
button.add(new Icon(value.isClicked() ? IconType.CHECK : IconType.TIMES));
sb.append(SafeHtmlUtils.fromTrustedString(button.toString()));
}
#Override
public void onBrowserEvent(Context context, Element parent, TableType value, NativeEvent event, ValueUpdater<TableType> valueUpdater) {
value.setClicked(!value.isClicked());
// ... do stuff...
table.redrawRow(context.getIndex());
}
};
table.addColumn(new Column<TableType, TableType>(buttonCell) {
#Override
public TableType getValue(TableType object) {
return object;
}
});
ArrayList<TableType> rowData = new ArrayList<TableType>();
rowData.add(new TableType("row 1"));
rowData.add(new TableType("row 2"));
...
table.setRowData(rowData);
And example table's data type keeping the clicked state:
public class TableType {
String text;
boolean clicked = false;
public TableType(String text) {
this.text = text;
}
public String getText() {
return text;
}
public boolean isClicked() {
return clicked;
}
public void setClicked(boolean clicked) {
this.clicked = clicked;
}
}
As for extending the IconType enum - no, you can not extend an enum in Java. See this question for example: Can enums be subclassed to add new elements?.
You could try to add your own CSS class but this should be asked as another question to get precise answers.
I'm working on an Eclipse RCP application and I'm trying to update an expression value which is provided by MySourceProvider according to selection changes on a TableViewer in MyEditorPart.
MyEditorPart instance defines a TableViewer like this:
public class MyEditorPart extends EditorPart {
#Override
public void createPartControl(Composite parent) {
TableViewer tableviewer = new TableViewer(parent, SWT.CHECK);
tableviewer.setContentProvider(ArrayContentProvider.getInstance());
getSite().setSelectionProvider(tableViewer);
...
MySourceProvider have some expression values like this:
public class MySourceProvider extends AbstractSourceProvider {
public static final String EXPR = "org.xyz.isEntrySelected";
// other expressions
#Override
public String[] getProvidedSourceNames() {
return new String[] { EXPR,
// other expressions
};
}
#Override
public Map getCurrentState() {
HashMap<String, Object> map = new HashMap<String, Object>(1);
map.put(EXPR, expr_value); // expr_value calculated by the listener
// other expressions
return map;
}
I want to change expr_value according to selection changes on TableViewer.
I registered the listener like this:
window.getSelectionService().addPostSelectionListener(MyEditorPartId, selectionListener);
private final ISelectionListener selectionListener = new SelectionListener() {
#Override
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
handleEvent();
}
};
The listener registers successfully but gets notified only once if I clicked somewhere on MyEditorPart (not just TableViewer but the whole editor). To get notified again, I have to click on some other view (or editor) part to lose focus and then click again on MyEditorPart.
1. Why does the listener gets notified only once when MyEditorPart re-gains focus?
2. How to listen only to selection changes to TableViewer rows?
What am I missing here? What is the proper way to listen to selection changes?
Thanks in advance.
What you need is not a SelectionListener, but a SelectionChangedListener.
With this you can write the following code:
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
#Override
public void selectionChanged(SelectionChangedEvent event) {
IStructuredSelection selection = viewer.getStructuredSelection();
Object firstElement = selection.getFirstElement();
// do something with it
}
});
It does appear that this form of addPostSelectionListener only fires when the part becomes active. Use the:
addPostSelectionListener(ISelectionListener listener)
form of the listener which is called for every selection change.
You can then test the IWorkbenchPart id in the listener:
#Override
public void selectionChanged(final IWorkbenchPart part, final ISelection selection)
{
if (MyEditorPartId.equals(part.getSite().getId()))
{
// your code
}
}
I'm implementing a program within Swing, and I've read Nirmal's implementation of this pattern in Swing, which seems to show a rather elegant handling of the whole "separation of responsibilities" concept.
However, as I'm developing a more complicated program than the one posted by Nirml, which consists of a single JFrame container, I look for guidance as how to implement MVC properly.
My program will be consisting of sub-containers and such. I am curious as to how the Controller is supposed to implement the logic behind defining and assigining all the listeners of the View.. or if the controller defining the listeners for every single View component is even practical?
It would seem that I would need a method in the View's top-level container to allow the Controller to invoke the view to add a Listener to the component in question? and so I would need a chain of methods, each passing down the listener from the top-level container, to the immediate container holding the component.. culminating with the container invoking addActionListener() on it.
Is this the proper way to handle listeners in MVC?
Is defining all listeners of every component in the View by the Controller mandatory in MVC, or a useful practice? This would also imply that I create methods in the top-level container(View) to give the Controller a way to assign listeners to every single component in sub-containers?
Okay, first things first, Swing implements a form of the MVC already, albeit in the form of VC-M. This means that you shouldn't try and constrain Swing to a pure MVC directly, as you'll be very disappointed and spend a lot of time trying to make hacks where they shouldn't be.
Instead, you can wrap a MVC around Swing, allowing it to work around the API instead.
To my mind, a controller doesn't need to know, nor should it care, how the view or model are implemented, but it should only care how it can work with them (I've had too many developers get hold of UI components and do things to/with them that they shouldn't have, and broken the API when we've changed the implementation. It's best to hide that kind of detail)
In this vein, you can think of a view as self contained entity - it has controls and it does stuff independent of the controller. The controller doesn't care about the implementation specifics. What it does care about is getting information and been told when some event, described by the contract, has occurred. It shouldn't care about HOW it was generated.
For example, say you have a login view. The controller only wants to know the user name and password that the user entered and when it should validate that information.
Let's say you implement the view/controller to expose the JTextField and JPasswordFields to start with, but later on, your users want the user name selection to be restricted to a specific list (possibly provided by the model). Now you have implementation details stuck in your controller which are no longer applicable and you have to manually change or create a new MVC for this new use case.
What if, instead, you simply stated that the view has a getter for the user name and password and some kind event listener which would tell the controller when the user wanted the credentials verified? Well now, you'd only need to provide a new view, no need to modify the controller. The controller won't care HOW these values are generated.
As to the greater aspect of your question.
My program will be consisting of sub-containers and such. I am curious
as to how the Controller is supposed to implement the logic behind
defining and assigning all the listeners of the View.. or if the
controller defining the listeners for every single View component is
even practical?
It would seem that I would need a method in the View's top-level
container to allow the Controller to invoke the view to add a Listener
to the component in question? and so I would need a chain of methods,
each passing down the listener from the top-level container, to the
immediate container holding the component.. culminating with the
container invoking addActionListener() on it.
Is this the proper way to handle listeners in MVC?
The general answer is, no, it's not the proper way.
Each sub view would become its own MVC, with it focusing on its own requirements. The parent MVC might use events or other functionality provided by the child MVC's to make updates or even modify the states of other child MVCs.
The important thing to remember here, is a view can act as a controller for other views, although, you might choose to have a series of controllers which the view would be allowed to manage.
Imagine something like a "wizard". It has a bunch of steps which collects various information from the user, each step needs to be valid before it can move on to the next step.
Now, you might be tempted to integrate navigation into this directly, but a better idea would be to separate the navigation details as its own MVC.
The wizard would, when asked, present a step to the user, the user would fill in the information, possibly triggering events. These events would then allow the navigation MVC to decide if the user can move to the next step or the previous step.
The two MVC's would be controlled by a third "master" MVC which would help manage the states (listening for events from the wizard and updating the state of the navigation)
Let's try an example with a question that gets asked way to much around here, a quiz!
A quiz has questions, each question has a prompt, a correct answer, a series of possible answers and we also want to store the resulting answer from the user.
The Quiz API
So, below we have the basic outline of the quiz MVC, we have a question, which is managed by a model, there is a controller and a view and series of observers (listeners)
The Contracts (interfaces)
public interface Question {
public String getPrompt();
public String getCorrectAnswer();
public String getUserAnswer();
public String[] getOptions();
public boolean isCorrect();
}
/**
* This is a deliberate choice to separate the update functionality
* No one but the model should ever actually -apply- the answer to the
* question
*/
public interface MutableQuestion extends Question {
public void setUserAnswer(String userAnswer);
}
public interface QuizModel {
public void addQuizObserver(QuizModelObserver observer);
public void removeQuizObserver(QuizModelObserver observer);
public Question getNextQuestion();
public Question getCurrentQuestion();
public int size();
public int getScore();
public void setUserAnswerFor(Question question, String answer);
}
public interface QuizModelObserver {
public void didStartQuiz(QuizModel quiz);
public void didCompleteQuiz(QuizModel quiz);
public void questionWasAnswered(QuizModel model, Question question);
}
public interface QuizView extends View {
public void setQuestion(Question question);
public boolean hasAnswer();
public String getUserAnswer();
public void addQuizObserver(QuizViewObserver observer);
public void removeQuizObserver(QuizViewObserver observer);
}
public interface QuizViewObserver {
public void userDidChangeAnswer(QuizView view);
}
public interface QuizController {
public QuizModel getModel(); // This is the model
public QuizView getView();
public void askNextQuestion();
}
I, personally, work to the principle of "code to interface (not implementation)", I've also deliberately gone overboard with the idea to demonstrate the point.
If you look closely, you will note that neither the view or model actually have any relationship to each other. This is all controlled via, the controller
One of the things I've done here is to provide the controller with a askNextQuestion, because the controller doesn't know when that should occur (you might think about using the userDidChangeAnswer, but that would mean that the user only gets a single attempt to answer the question, kind of mean)
The implementation
Now, normally, I like to have some abstract implementations laying around to fill out the "common" functionality, I've forgone that for the most part and gone directly to the default implementation, this done mostly for demonstration purposes.
public class DefaultQuestion implements MutableQuestion {
private final String prompt;
private final String correctAnswer;
private String userAnswer;
private final String[] options;
public DefaultQuestion(String prompt, String correctAnswer, String... options) {
this.prompt = prompt;
this.correctAnswer = correctAnswer;
this.options = options;
}
#Override
public String getPrompt() {
return prompt;
}
#Override
public String getCorrectAnswer() {
return correctAnswer;
}
#Override
public String getUserAnswer() {
return userAnswer;
}
#Override
public String[] getOptions() {
List<String> list = new ArrayList<>(Arrays.asList(options));
Collections.shuffle(list);
return list.toArray(new String[list.size()]);
}
public void setUserAnswer(String userAnswer) {
this.userAnswer = userAnswer;
}
#Override
public boolean isCorrect() {
return getCorrectAnswer().equals(getUserAnswer());
}
}
public abstract class AbstractQuizModel implements QuizModel {
private List<QuizModelObserver> observers;
public AbstractQuizModel() {
observers = new ArrayList<>(25);
}
#Override
public void addQuizObserver(QuizModelObserver observer) {
observers.add(observer);
}
#Override
public void removeQuizObserver(QuizModelObserver observer) {
observers.remove(observer);
}
protected void fireDidStartQuiz() {
for (QuizModelObserver observer : observers) {
observer.didStartQuiz(this);
}
}
protected void fireDidCompleteQuiz() {
for (QuizModelObserver observer : observers) {
observer.didCompleteQuiz(this);
}
}
protected void fireQuestionWasAnswered(Question question) {
for (QuizModelObserver observer : observers) {
observer.questionWasAnswered(this, question);
}
}
}
public class DefaultQuizModel extends AbstractQuizModel {
private List<MutableQuestion> questions;
private Iterator<MutableQuestion> iterator;
private MutableQuestion currentQuestion;
private boolean completed;
private int score;
public DefaultQuizModel() {
questions = new ArrayList<>(50);
}
public void add(MutableQuestion question) {
questions.add(question);
}
public void remove(MutableQuestion question) {
questions.remove(question);
}
#Override
public Question getNextQuestion() {
if (!completed && iterator == null) {
iterator = questions.iterator();
fireDidStartQuiz();
}
if (iterator.hasNext()) {
currentQuestion = iterator.next();
} else {
completed = true;
iterator = null;
currentQuestion = null;
fireDidCompleteQuiz();
}
return currentQuestion;
}
#Override
public Question getCurrentQuestion() {
return currentQuestion;
}
#Override
public int size() {
return questions.size();
}
#Override
public int getScore() {
return score;
}
#Override
public void setUserAnswerFor(Question question, String answer) {
if (question instanceof MutableQuestion) {
((MutableQuestion) question).setUserAnswer(answer);
if (question.isCorrect()) {
score++;
}
fireQuestionWasAnswered(question);
}
}
}
public class DefaultQuizController implements QuizController {
private QuizModel model;
private QuizView view;
public DefaultQuizController(QuizModel model, QuizView view) {
this.model = model;
this.view = view;
}
#Override
public QuizModel getModel() {
return model;
}
#Override
public QuizView getView() {
return view;
}
#Override
public void askNextQuestion() {
Question question = getModel().getCurrentQuestion();
if (question != null) {
String answer = getView().getUserAnswer();
getModel().setUserAnswerFor(question, answer);
}
question = getModel().getNextQuestion();
getView().setQuestion(question);
}
}
public class DefaultQuizViewPane extends JPanel implements QuizView {
private final JLabel question;
private final JPanel optionsPane;
private final ButtonGroup bg;
private final List<JRadioButton> options;
private String userAnswer;
private final List<QuizViewObserver> observers;
private final AnswerActionListener answerActionListener;
private final GridBagConstraints optionsGbc;
protected DefaultQuizViewPane() {
setBorder(new EmptyBorder(4, 4, 4, 4));
question = new JLabel();
optionsPane = new JPanel(new GridBagLayout());
optionsPane.setBorder(new EmptyBorder(4, 4, 4, 4));
answerActionListener = new AnswerActionListener();
optionsGbc = new GridBagConstraints();
optionsGbc.gridwidth = GridBagConstraints.REMAINDER;
optionsGbc.weightx = 1;
optionsGbc.anchor = GridBagConstraints.WEST;
options = new ArrayList<>(25);
bg = new ButtonGroup();
observers = new ArrayList<>(25);
setLayout(new BorderLayout());
add(question, BorderLayout.NORTH);
add(optionsPane);
}
protected void reset() {
question.setText(null);
for (JRadioButton rb : options) {
rb.removeActionListener(answerActionListener);
bg.remove(rb);
optionsPane.remove(rb);
}
options.clear();
}
#Override
public void setQuestion(Question question) {
reset();
if (question != null) {
this.question.setText(question.getPrompt());
for (String option : question.getOptions()) {
JRadioButton rb = makeRadioButtonFor(option);
options.add(rb);
optionsPane.add(rb, optionsGbc);
}
optionsPane.revalidate();
revalidate();
repaint();
}
}
#Override
public void addQuizObserver(QuizViewObserver observer) {
observers.add(observer);
}
#Override
public void removeQuizObserver(QuizViewObserver observer) {
observers.remove(observer);
}
protected void fireUserDidChangeAnswer() {
for (QuizViewObserver observer : observers) {
observer.userDidChangeAnswer(this);
}
}
protected JRadioButton makeRadioButtonFor(String option) {
JRadioButton btn = new JRadioButton(option);
btn.addActionListener(answerActionListener);
bg.add(btn);
return btn;
}
#Override
public boolean hasAnswer() {
return userAnswer != null;
}
#Override
public String getUserAnswer() {
return userAnswer;
}
#Override
public JComponent getViewComponent() {
return this;
}
protected class AnswerActionListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
userAnswer = e.getActionCommand();
fireUserDidChangeAnswer();
}
}
}
Really nothing fancy here. About the only thing of significant interest here is how the controller is managing the events between the model and the view
The Navigation API
The navigation API is pretty basic. It allows you to control if the user can actually navigate to the next or previous element (if the actions should be made available to the user) as well as disable either of the actions at any time
(Again, I've focused on a simple design, realistically, it would be nice to have some control over modifying the state of the model to change in which directions the navigation can work, but I've left this out on purpose to keep it simple)
The Contracts (interfaces)
public enum NavigationDirection {
NEXT, PREVIOUS;
}
public interface NavigationModel {
public boolean canNavigate(NavigationDirection direction);
public void addObserver(NavigationModelObserver observer);
public void removeObserver(NavigationModelObserver observer);
public void next();
public void previous();
}
public interface NavigationModelObserver {
public void next(NavigationModel view);
public void previous(NavigationModel view);
}
public interface NavigationController {
public NavigationView getView();
public NavigationModel getModel();
public void setDirectionEnabled(NavigationDirection navigationDirection, boolean b);
}
public interface NavigationView extends View {
public void setNavigatable(NavigationDirection direction, boolean navigtable);
public void setDirectionEnabled(NavigationDirection direction, boolean enabled);
public void addObserver(NavigationViewObserver observer);
public void removeObserver(NavigationViewObserver observer);
}
public interface NavigationViewObserver {
public void next(NavigationView view);
public void previous(NavigationView view);
}
The implementation
public static class DefaultNavigationModel implements NavigationModel {
private Set<NavigationDirection> navigatableDirections;
private List<NavigationModelObserver> observers;
public DefaultNavigationModel() {
this(true, true);
}
public DefaultNavigationModel(boolean canNavigateNext, boolean canNavigatePrevious) {
navigatableDirections = new HashSet<>(2);
observers = new ArrayList<>(25);
setCanNavigate(NavigationDirection.NEXT, canNavigateNext);
setCanNavigate(NavigationDirection.PREVIOUS, canNavigatePrevious);
}
public void setCanNavigate(NavigationDirection direction, boolean canNavigate) {
if (canNavigate) {
navigatableDirections.add(direction);
} else {
navigatableDirections.remove(direction);
}
}
#Override
public boolean canNavigate(NavigationDirection direction) {
return navigatableDirections.contains(direction);
}
#Override
public void addObserver(NavigationModelObserver observer) {
observers.add(observer);
}
#Override
public void removeObserver(NavigationModelObserver observer) {
observers.remove(observer);
}
protected void fireMoveNext() {
for (NavigationModelObserver observer : observers) {
observer.next(this);
}
}
protected void fireMovePrevious() {
for (NavigationModelObserver observer : observers) {
observer.previous(this);
}
}
#Override
public void next() {
fireMoveNext();
}
#Override
public void previous() {
fireMovePrevious();
}
}
public static class DefaultNavigationController implements NavigationController {
private final NavigationModel model;
private final NavigationView view;
public DefaultNavigationController(NavigationModel model, NavigationView view) {
this.model = model;
this.view = view;
view.setNavigatable(NavigationDirection.NEXT, model.canNavigate(NavigationDirection.NEXT));
view.setNavigatable(NavigationDirection.PREVIOUS, model.canNavigate(NavigationDirection.PREVIOUS));
view.addObserver(new NavigationViewObserver() {
#Override
public void next(NavigationView view) {
if (getModel().canNavigate(NavigationDirection.NEXT)) {
getModel().next();
}
}
#Override
public void previous(NavigationView view) {
if (getModel().canNavigate(NavigationDirection.PREVIOUS)) {
getModel().previous();
}
}
});
}
#Override
public NavigationView getView() {
return view;
}
#Override
public NavigationModel getModel() {
return model;
}
#Override
public void setDirectionEnabled(NavigationDirection navigationDirection, boolean enabled) {
getView().setDirectionEnabled(navigationDirection, enabled);
}
}
public static class DefaultNavigationViewPane extends JPanel implements NavigationView {
private final List<NavigationViewObserver> observers;
private final JButton btnNext;
private final JButton btnPrevious;
public DefaultNavigationViewPane() {
btnNext = new JButton("Next >");
btnNext.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fireMoveNext();
}
});
btnPrevious = new JButton("< Previous");
btnPrevious.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fireMovePrevious();
}
});
setLayout(new FlowLayout(FlowLayout.RIGHT));
add(btnPrevious);
add(btnNext);
observers = new ArrayList<>();
}
#Override
public void addObserver(NavigationViewObserver observer) {
observers.add(observer);
}
#Override
public void removeObserver(NavigationViewObserver observer) {
observers.remove(observer);
}
protected void fireMoveNext() {
for (NavigationViewObserver observer : observers) {
observer.next(this);
}
}
protected void fireMovePrevious() {
for (NavigationViewObserver observer : observers) {
observer.previous(this);
}
}
#Override
public JComponent getViewComponent() {
return this;
}
#Override
public void setNavigatable(NavigationDirection direction, boolean navigtable) {
switch (direction) {
case NEXT:
btnNext.setVisible(navigtable);
break;
case PREVIOUS:
btnPrevious.setVisible(navigtable);
break;
}
}
#Override
public void setDirectionEnabled(NavigationDirection direction, boolean enabled) {
switch (direction) {
case NEXT:
btnNext.setEnabled(enabled);
break;
case PREVIOUS:
btnPrevious.setEnabled(enabled);
break;
}
}
}
The Quiz Master
Now, these are two distinct APIs, they have nothing in common, so, we need some kind of controller to bridge them
The contracts (the interfaces)
public interface QuizMasterController {
public QuizController getQuizController();
public NavigationController getNavigationController();
public QuizMasterView getView();
}
public interface QuizMasterView extends View {
public NavigationController getNavigationController();
public QuizController getQuizController();
public void showScoreView(int score, int size);
public void showQuestionAndAnswerView();
}
Okay, so you're probably asking yourself the obvious question, where's the model? Well, it doesn't need one, it's just a bridge between the navigation and quiz APIs, it doesn't manage any data of it's own...
The implementations
public class DefaultQuizMasterController implements QuizMasterController {
private QuizController quizController;
private NavigationController navController;
private QuizMasterView view;
public DefaultQuizMasterController(QuizController quizController, NavigationController navController) {
this.quizController = quizController;
this.navController = navController;
view = new DefaultQuizMasterViewPane(quizController, navController);
// Setup the initial state
quizController.askNextQuestion();
navController.getModel().addObserver(new NavigationModelObserver() {
#Override
public void next(NavigationModel view) {
getQuizController().askNextQuestion();
getNavigationController().setDirectionEnabled(NavigationDirection.NEXT, false);
}
#Override
public void previous(NavigationModel view) {
// NOOP
}
});
quizController.getView().addQuizObserver(new QuizViewObserver() {
#Override
public void userDidChangeAnswer(WizeQuiz.QuizView view) {
getNavigationController().setDirectionEnabled(NavigationDirection.NEXT, true);
}
});
quizController.getModel().addQuizObserver(new QuizModelObserver() {
#Override
public void didStartQuiz(QuizModel quiz) {
getView().showQuestionAndAnswerView();
}
#Override
public void didCompleteQuiz(QuizModel quiz) {
getView().showScoreView(quiz.getScore(), quiz.size());
getNavigationController().setDirectionEnabled(NavigationDirection.NEXT, false);
}
#Override
public void questionWasAnswered(QuizModel model, Question question) {
}
});
navController.setDirectionEnabled(NavigationDirection.NEXT, false);
}
#Override
public QuizController getQuizController() {
return quizController;
}
#Override
public NavigationController getNavigationController() {
return navController;
}
#Override
public QuizMasterView getView() {
return view;
}
}
public class DefaultQuizMasterViewPane extends JPanel implements QuizMasterView {
private QuizController quizController;
private NavigationController navController;
private QuestionAndAnswerView qaView;
private ScoreView scoreView;
private CardLayout cardLayout;
public DefaultQuizMasterViewPane(QuizController quizController, NavigationController navController) {
this.quizController = quizController;
this.navController = navController;
quizController.getModel().addQuizObserver(new QuizModelObserver() {
#Override
public void didStartQuiz(QuizModel quiz) {
}
#Override
public void didCompleteQuiz(QuizModel quiz) {
}
#Override
public void questionWasAnswered(QuizModel model, Question question) {
qaView.updateScore();
}
});
scoreView = new ScoreView();
qaView = new QuestionAndAnswerView();
qaView.updateScore();
cardLayout = new CardLayout();
setLayout(cardLayout);
add(qaView, "view.qa");
add(scoreView, "view.score");
}
#Override
public JComponent getViewComponent() {
return this;
}
#Override
public NavigationController getNavigationController() {
return navController;
}
#Override
public QuizController getQuizController() {
return quizController;
}
#Override
public void showScoreView(int score, int size) {
scoreView.updateScore();
cardLayout.show(this, "view.score");
}
#Override
public void showQuestionAndAnswerView() {
cardLayout.show(this, "view.qa");
}
protected class QuestionAndAnswerView extends JPanel {
private JLabel score;
public QuestionAndAnswerView() {
setLayout(new BorderLayout());
add(getQuizController().getView().getViewComponent());
JPanel south = new JPanel(new BorderLayout());
south.add(getNavigationController().getView().getViewComponent(), BorderLayout.SOUTH);
score = new JLabel();
score.setHorizontalAlignment(JLabel.RIGHT);
south.add(score, BorderLayout.NORTH);
add(south, BorderLayout.SOUTH);
}
protected void updateScore() {
score.setText(getQuizController().getModel().getScore() + "/" + getQuizController().getModel().size());
}
}
protected class ScoreView extends JPanel {
private JLabel score;
public ScoreView() {
setLayout(new GridBagLayout());
score = new JLabel("You scored:");
add(score);
}
protected void updateScore() {
score.setText("You scored: " + getQuizController().getModel().getScore() + "/" + getQuizController().getModel().size());
}
}
}
Now, the implementation is interesting, it actually has two "states" or "views", the "question and answer" view and the "score view". This, again, is deliberate, because I really didn't want ANOTHER MVC. The Q&A view is already managing two MVCs any way :P
Basically, what this does is monitors the quiz API for when the user changes the answer to a question, it then tells the navigation API that it can move to the next question. It monitors the start and completed events as well, presenting the required view for those states.
It is also monitoring the navigation API for navigation events. In this example, we can only move in a single direction and even if the navigation API was configured to do otherwise, the quiz API does not provide that functionality
Put it together
Now, I've chosen to deliberately build each section separately, conceivably, you could have the QuizMasterController build the Navigation API itself, as it knows that the quiz API only allows for forward navigation, equally we could change the navigation API to allow those states to be modified via the model or the model changed, these are all viable solutions, I've just gone for a direct example.
NavigationModel navigationModel = new DefaultNavigationModel(true, false);
NavigationView navigationView = new DefaultNavigationViewPane();
NavigationController navigationController = new NavWiz.DefaultNavigationController(navigationModel, navigationView);
DefaultQuizModel quizModel = new DefaultQuizModel();
quizModel.add(new DefaultQuestion(
"Which pop duo was the first western band to play in The Peoples Republic of China?",
"Wham",
"Wham", "Simon and Garfunkel", "Chas and Dave", "Right Said Fred"));
quizModel.add(new DefaultQuestion(
"Timber selected from how many fully grown oak trees were needed to build a large 3 decker Royal Navy battle ship in the 18th century?",
"3,500",
"50", "500", "1,500", "3,500"));
quizModel.add(new DefaultQuestion(
"Speed skating originated in which country?",
"Netherlands",
"Russia", "Netherlands", "Canada", "Norway"));
quizModel.add(new DefaultQuestion(
"Off the coast of which country did the Amoco Cadiz sink?",
"France",
"South Africa", "France", "USA", "Spain"));
quizModel.add(new DefaultQuestion(
"The song 'An Englishman in New York' was about which man?",
"Quentin Crisp",
"Quentin Crisp", "Sting", "John Lennon", "Gordon Sumner"));
QuizView quizView = new DefaultQuizViewPane();
QuizController quizController = new DefaultQuizController(quizModel, quizView);
QuizMasterController quizMasterController = new DefaultQuizMasterController(quizController, navigationController);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(quizMasterController.getView().getViewComponent());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
And finally we end up with something like...
This is nothing if not rough, but is designed to provide some ideas into how you might accomplish complex, compound MVCs
In my JavaFX TableView I have one TableColumn on which I have set Cell Factory to render ProgressBar and for other TableColumns I have set Cell Factory to show ToolTip. Like the image below. Second Column is showing Progress Bar and other 3 Columns are render to show Tool tip, that has simple string values to show.
I was getting issue in which the TableView was not displaying/showing updated values in the table i.e UI is not validating/refreshing/painting the TableView elements. If I clicked on ColumnHeader to sort any column then only I can see the TableView updating. Manually sort the table column to refresh the table content is not making sense so I have searched and found solution to show/hide the Table Columns for updating the Table View.
To resolved the issue I have written a code below to solve the TableView Updating/Refreshing issue but due to this code now ToolTip are not getting visible.
Code to Update Table View after each specific interval
class TableProgressBarUpdator implements Runnable {
TableView table;
public TableProgressBarUpdator(TableView fxtable) {
table = fxtable;
}
public void start() {
new Thread(this).start();
}
public void run() {
while (keepUpdating) {
try {
updateProgressbar();
Thread.sleep(1000);
} catch (Exception e) {
LogHandler.doErrorLogging("Error while updating tables cell", e);
}
}
LogHandler.doDebugLogging("Table process repainting is completed.");
}
private void updateProgressbar() throws Exception {
Platform.runLater(new Runnable() {
#Override
public void run() {
((TableColumn) table.getColumns().get(0)).setVisible(false);
((TableColumn) table.getColumns().get(0)).setVisible(true);
}
});
}
}
Start Updating Table View
public void startUpdatingTableProgress() {
keepUpdating = true;
TableProgressBarUpdator tpu = new TableProgressBarUpdator(table);
tpu.start();
}
Stop Updating Table View
public void stopUpdatingTableProgress() {
keepUpdating = false;
}
Adding more code that is showing render classes to show Progress bar and display Tool Tip.
Code to show the Progress Bar Table View.
public static class ProgressBarTableCell<S, T> extends TableCell<S, T> {
private final ProgressBar progressBar;
private ObservableValue<T> ov;
public ProgressBarTableCell() {
this.progressBar = new ProgressBar();
progressBar.setPrefHeight(23);
setAlignment(Pos.CENTER);
}
#Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (item == null) {
setGraphic(null);
setText(null);
} else {
if (item.toString().equalsIgnoreCase("Processing")) {
Platform.runLater(new Runnable() {
#Override
public void run() {
if (getGraphic() == null) {
setGraphic(progressBar);
progressBar.setProgress(-1);
} else {
ProgressBar objpProgressBar = (ProgressBar) getGraphic();
objpProgressBar.setProgress(-1);
}
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
});
} else {
Platform.runLater(new Runnable() {
#Override
public void run() {
if (getGraphic() == null) {
setGraphic(progressBar);
progressBar.setProgress(0);
} else {
ProgressBar objpProgressBar = (ProgressBar) getGraphic();
objpProgressBar.setProgress(0);
}
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
});
}
}
}
}
Code to Show the Tool Tip
public class ToolTip extends TableCell {
#Override
protected void updateItem(Object object, boolean selected) {
if (object == null) {
setGraphic(null);
setText(null);
}else{
setText(object.toString());
setTooltip(new Tooltip(object.toString()));
}
}
}
Issue -
If I comment-out these two lines from TableProgressBarUpdator Class then I am able to see Tool Tip for each cell values in 1st, 3rd and 4th column but now Table View contents are not updating/refreshing and when I UN-comment these lines I am unable to see the Tool Tip.
((TableColumn) table.getColumns().get(0)).setVisible(false);
((TableColumn) table.getColumns().get(0)).setVisible(true);
In all due to these two lines my Tool Tip Render is not working and If I remove these two lines then Table View Content are not Refreshing/Updating.
You don't need to update TableView manually. may be there are problem in your class associated with that TableView's column.
You have to create class as given below :
public static class Test{
private StringProperty name;
private Test() {
name = new SimpleStringProperty();
}
public Test(String name) {
this.name = new SimpleStringProperty(name);
}
public void setName(String name) {
this.name.set(name);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
}
Are you sure you need the Platform.runLater() call within your ProgressBarTableCell? I would expect it to already be in the Application thread. That could cause the progress bar update to be placed at the end of the queue in the Application thread, after the scheduled table update.
Is the value for your TableCell wrapped in an ObservableProperty (looks like you should have an SimpleStringProperty)? If you did, the table should recognize that it needs a refresh, and you shouldn't have to resort to toggling the column visibility as a hack to force table refreshing.
I have subclassed org.eclipse.swt.widgets.Composite to create a new composite control. I want to capture MouseEnter and MouseExit events in this control but the problem I have is that when the mouse is hovered over a component in the control (say, a Label) the MouseExit event is fired, even though the label is part of the whole Composite.
Is there any way to stop this event being fired? I only want to see the event if the mouse leaves the total boundary of the control. Here is some example code to show you what I mean.
public class MyControl extends Composite{
Label label;
public MyControl(Composite parent, String label) {
super(parent, SWT.NONE);
label = new Label(this,0);
label.setText(label);
this.addListener(SWT.MouseEnter, new Listener() {
#Override
public void handleEvent(Event event) {
// handle this event
}
});
this.addListener(SWT.MouseExit, new Listener() {
#Override
public void handleEvent(Event event) {
// handle this event
}
});
}
}
You can simply put an logic in your event handler to see if the control is a child of your new control and ignore it. Something like the following: (I haven't tested the code, but I think this should work for you)
this.addListener(SWT.MouseExit, new Listener() {
#Override
public void handleEvent(Event event) {
for (Control control : ParentClass.this.getChildren()) {
if (control == event.item)
return;
}
// handler logic goes here
}
});
I solved the same problem (MouseExit is sent to a Composite when the mouse enters one of its children) with an event filter. Here's the code.
public class MouseTracker implements Listener {
static MouseTracker instance;
private static class Item {
Composite composite;
boolean inside;
MouseTrackListener listener;
}
private List<Item> listeners = new ArrayList<>();
private MouseTracker() {
}
public static MouseTracker getInstance() {
if (instance == null)
instance = new MouseTracker();
return instance;
}
private void install() {
Display.getCurrent().addFilter(SWT.MouseEnter, this);
Display.getCurrent().addFilter(SWT.MouseExit, this);
Display.getCurrent().addFilter(SWT.Resize, this);
}
private void uninstall() {
Display.getCurrent().removeFilter(SWT.MouseEnter, this);
Display.getCurrent().removeFilter(SWT.MouseExit, this);
Display.getCurrent().removeFilter(SWT.Resize, this);
}
public void addMouseTrackListener(Composite c, MouseTrackListener listener) {
if (listeners.isEmpty())
install();
Item i = new Item();
i.composite = c;
i.inside = false;
i.listener = listener;
listeners.add(i);
}
public void removeMouseTrackListener(Composite c, MouseTrackListener listener) {
listeners.removeIf((i) -> i.composite == c && i.listener == listener);
if (listeners.isEmpty())
uninstall();
}
public void handleEvent(Event e) {
boolean hasDisposed = false;
for (Item i : listeners) {
Composite c = i.composite;
if (c.isDisposed())
hasDisposed = true;
else {
Point p = Display.getCurrent().getCursorLocation();
boolean containsMouse = c.getBounds().contains(c.getParent().toControl(p));
if (i.inside != containsMouse) {
i.inside = containsMouse;
if (containsMouse)
i.listener.mouseEnter(new MouseEvent(e));
else
i.listener.mouseExit(new MouseEvent(e));
}
}
}
if (hasDisposed) {
listeners.removeIf((i) -> i.composite.isDisposed());
if (listeners.isEmpty())
uninstall();
}
}
}