I have two JPanels nested inside a cardPanl(with a cardLayout).
When switching between pages I need to have new instance of the page created. For example when I switch from homePage to captchaPage, I will replace the current homePage with a new instance of 'HomePage'. same thing goes when switching from captchaPage to homePage.
I will create the new instances without any problem but what I see on the screen is the old view of the JPanels meaning they do not get repainted.
I've searched for this problem and almost all the solutions suggest calling revalidate(), validate() or repaint() on the panel.
I've did it all and still I get the old view. I'm sure that creating the new instances is done successfully because when printing the capthcha in the console i see that it changes but the view remains the same.
Here is my structure:
BasicPage.java
public class BasePage extends JPanel {
protected JFrame parent;
protected String name;
public BasePage(JFrame parent, String name) {
this.parent = parent;
this.name = name;
// ...
}
}
CaptchaPage.java
public class CaptchaPage extends BasePage {
private String challenge;
public CaptchaPage(JFrame parent, String name) {
super(parent, name);
challenge = new BigInteger(130, new SecureRandom()).toString(32);
challenge = challenge.length() > 5 ? challenge.substring(0, 5) : challenge;
JLabel label = new JLabel(challenge);
this.add(label);
}
}
Dashboard.java
public class Dashboard extends JFrame {
private JPanel cardPanel;
private BasePage homePage;
private BasePage captchaPage;
public Dashboard() {
cardPanel = new JPanel();
cardPanel.setLayout(new CardLayout());
homePage = new HomePage(this, "0");
captchaPage = new CaptchaPage(this, "1");
cardPanel.add(homePage, "0");
cardPanel.add(captchaPage, "1");
this.add(cardPanel);
}
protected void switchPage(String name) {
((CardLayout)cardPanel.getLayout()).show(cardPanel, name);
if (name.equals("1")) {
homePage = new HomePage(this, "0");
homePage.revalidate();
}
else {
captchaPage = new CaptchaPage(this, "1");
captchaPage.revalidate();
}
}
}
Answer
BasePage page = new HomePage(this, "0");
cardPanel.add(page, "0");
cardPanel.revalidate();
homePage = page;
You added panels to the CardLayout with the following code which is correct:
cardPanel.add(homePage, "0");
cardPanel.add(captchaPage, "1");
Now you are trying to update the CardLayout with code like:
homePage = new HomePage(this, "0");
That will not work. All you are doing is changing the reference of the homepage variable. You have not added the component to the CardLayout.
To change the panel then code should be the same as the code you used to add the panel initially:
JPanel homepage = new HomePage(...);
cardPanel.add(...);
Why are you changing the components on the panel? Why does the homepage change. Sounds like a strange design to me.
Related
I have a pretty simple class that basically is just an AppLayout with some Tab.
Now my issue. I am not able to find a smart way to display different contents for the Tabs-class. Is there any interface or something that can be called to differ the content for the Tab?
class MainAppView extends AppLayout {
public MainAppView()
{
createDrawerAndAddToAppView();
}
void createDrawerAndAddToAppView()
{
Tabs tabs = createTabsForDrawer();
tabs.setOrientation(Tabs.Orientation.VERTICAL);
addToDrawer(tabs);
H1 a = new H1("Test"); // Is displayed as content for every Tab
tabs.addSelectedChangeListener(selectedChangeEvent ->
/**
* How to get the specific content of a Tab here?
*/
//selectedChangeEvent.getSelectedTab(). //getContent() and put in super.setContent()?
super.setContent(a)); // Displays 'Test' as content for every Tab
// The Listener shall display the specific content of the getSelectedTab()
}
private Tabs createTabsForDrawer()
{
return new Tabs(
new Tab("Home"),
new Tab("Dummy"),
new Tab("Test"));
}
}
Here is one example, using a map to keep track of which content belongs to which tab. In reality your tab content would be more complicated, and maybe be created in it's own method.
#Route
public class TabTest extends VerticalLayout {
private Map<Tab, Component> tabComponentMap = new LinkedHashMap<>();
public TabTest() {
Tabs tabs = createTabs();
Div contentContainer = new Div();
add(tabs, contentContainer);
tabs.addSelectedChangeListener(e -> {
contentContainer.removeAll();
contentContainer.add(tabComponentMap.get(e.getSelectedTab()));
});
// Set initial content
contentContainer.add(tabComponentMap.get(tabs.getSelectedTab()));
}
private Tabs createTabs() {
tabComponentMap.put(new Tab("Show some text"), new H1("This is the text tab"));
tabComponentMap.put(new Tab("Show a Combo Box"), new ComboBox<String>());
tabComponentMap.put(new Tab("Show a button"), new Button("Click me and nothing happens"));
return new Tabs(tabComponentMap.keySet().toArray(new Tab[]{}));
}
}
You can do something similar with routes also, but then you would probably want your containing component to be a RouterLayout. Also this requires a bit more logic if you want to automatically select the correct tab after navigating from somewhere else.
#Route
public class TabTest extends VerticalLayout implements RouterLayout {
private Map<Tab, String> tabToUrlMap = new LinkedHashMap<>();
private Div contentContainer = new Div();
public TabTest() {
Tabs tabs = createTabs();
Div contentContainer = new Div();
contentContainer.setSizeFull();
add(tabs, contentContainer);
tabs.addSelectedChangeListener(e ->
UI.getCurrent().navigate(tabToUrlMap.get(e.getSelectedTab())));
}
private Tabs createTabs() {
RouteConfiguration routeConfiguration = RouteConfiguration.forApplicationScope();
tabToUrlMap.put(new Tab("View 1"), routeConfiguration.getUrl(TestView1.class));
tabToUrlMap.put(new Tab("View 2"), routeConfiguration.getUrl(TestView2.class));
tabToUrlMap.put(new Tab("View 3"), routeConfiguration.getUrl(TestView3.class));
return new Tabs(tabToUrlMap.keySet().toArray(new Tab[]{}));
}
#Override
public void showRouterLayoutContent(HasElement content) {
getElement().appendChild(content.getElement());
}
}
And an example view
#Route(layout = TabTest.class)
public class TestView3 extends VerticalLayout {
public TestView3() {
add("View 3");
}
}
I'm creating a Java application using Swing and the MVC design pattern.
The application is designed as follows:
There is a class QuizPanel that extends JPanel. This panel is the container that holds the main 'Screens' of my application.
Each 'screen' of my application is a separate class that extends JPanel. These JPanel's are added/removed from the QuizPanel as required.
The top level QuizPanel implements my interface Switchable. The Switchable interface consists of a single SwitchView(ViewState state) method. I pass this to each 'screen' instantiated in the QuizPanel top level panel, so they are able to call SwitchView when a button is pressed.
On the login screen, the user enters a pin and student ID, if these match a token in the database, I need to pass the Token object to another screen of my application (a question screen that I haven't implemented yet) or have it available somehow. The token is retrieved from an embedded Derby database.
The only way I can think of doing this is creating a utility class with static Token variable that can be accessed by the other classes (this seems like a nasty way to do it). Am I having trouble with this because the design of my application is flawed? Is there any technique I can use to pass the Token across the different screens of my application?
Main
public static void main(String[] args) {
QuizPanel quizPanel = new QuizPanel();
JFrame frame = new JFrame("Quiz");
frame.setPreferredSize(new Dimension(400, 400));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(quizPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
QuizPanel Class
public class QuizPanel extends JPanel implements Switchable{
private MainMenuPane mainMenuPane;
private RegisterPane registerPane;
private LoginPane loginPane;
public QuizPanel() {
setLayout(new BorderLayout());
registerPane = new RegisterPane();
RegisterController registerController = new RegisterController(registerPane, this);
mainMenuPane = new MainMenuPane();
MainMenuController mainMenuController = new MainMenuController(mainMenuPane, this);
loginPane = new LoginPane();
LoginController loginController = new LoginController(loginPane, this);
switchView(ViewState.MAINMENU_STATE);
}
#Override
public void switchView(ViewState state) {
System.out.println("Changing state: " + state);
switch (state) {
case REGISTER_STATE:
removeAll();
setLayout(new BorderLayout());
add(registerPane, BorderLayout.CENTER);
repaint();
revalidate();
break;
case MAINMENU_STATE:
removeAll();
setLayout(new BorderLayout());
add(mainMenuPane, BorderLayout.CENTER);
repaint();
revalidate();
break;
case LOGIN_STATE:
removeAll();
setLayout(new BorderLayout());
add(loginPane, BorderLayout.CENTER);
repaint();
revalidate();
break;
default:
System.out.println("UNREGISTERED STATE!");
break;
}
}
}
Login Controller
public class LoginController implements ILoginController, ILoginViewObserver {
private ILoginView view;
private LoginModel loginModel;
private Switchable parentView;
public LoginController(ILoginView view, Switchable parentView) {
this.view = view;
this.loginModel = new LoginModel();
this.parentView = parentView;
view.addLoginViewObserver(this);
}
#Override
public ILoginView getLoginView() {
return view;
}
#Override
public void submitButtonWasPressed(Token token) {
Token verifiedToken = loginModel.verifyToken(token);
if (verifiedToken != null) {
System.out.println("Token (" + token.token + ") successfully verified");
// How can I pass the token to the new JPanel the parent view will be displaying?
} else {
System.out.println("Token is invalid");
}
}
#Override
public void cancelButtonWasPressed() {
parentView.switchView(ViewState.MAINMENU_STATE);
}
}
LoginModel Class
public class LoginModel {
private List<Token> tokens;
public LoginModel() {
TokenDao tokenAccessObject = new TokenAccessObject();
tokens = tokenAccessObject.getAllTokens();
}
public Token verifyToken(Token providedToken) {
for (Token token : tokens) {
if (token.studentID == providedToken.studentID){
if (token.token.compareTo(providedToken.token) == 0) {
return token;
}
}
}
return null;
}
}
I think that in this case you can use Singleton pattern. This pattern should be used as rarely as possible, but in your case (common information which must be accessed from different classes of application) you can use it (IMHO).
But in your case you can also use one Swing feature.
Any Swing window has a root pane. And each JComponent that is laid
out in the window has access to this pane.
JComponent has also possibility to store some user data in a map,
called "client properties". Because JRootPane extends JComponent
you can store/retrieve your token is this map.
Here is a simple code:
public class TokenUtils {
private static final String TOKEN_PROPERTY = "token";
public static Token findToken(JComponent component) {
JRootPane root = component.getRootPane();
if (root != null) {
return Token.class.cast(root.getClientProperty(TOKEN_PROPERTY));
}
return null;
}
public static void putToken(JComponent component, Token token) {
JRootPane root = component.getRootPane();
if (root != null) {
root.putClientProperty(TOKEN_PROPERTY, token);
}
}
}
Important: if you use more than one window, you must put the token into the each of them.
A methode to pass the value of the token to the parent JPanel is to add a methode in your interface like setToken(int token) and a global variable in your Quiz panel
QuizPanel:
private int token;
#Override
public void setToken(int token){
this.token = token;
}
Swiched Interface:
public void setToken(int token);
Login:
parentView.setToken(token);
Than you say parentView.setToken(token) in your LoginController. Now the token variable in the QuizPanel will be set.
You could save the token to a file then on the jpanel read that file to get the token
I'm writing a GUI app in Java using the MVP design pattern. JButton objects belong in the View class and the ActionListener objects belong in the Presenter. I'm looking for a concise way to allow the Presenter to add ActionListeners to the View's JButtons without either (1) making the buttons public and (2) without having to add a bunch of methods to the view that look like
private JButton foo;
private JButton bar;
public void addActionListenerToButtonFoo(ActionListener l) {
foo.addActionListener(l);
}
public void addActionListenerToButtonBar(ActionListener l) {
bar.addActionListener(l);
}
// (imagine typing 10 more of these trivial functions and having
// them clutter up your code)
I found one technique that works reasonably well:
public class View {
class WrappedJButton {
private JButton b;
public WrappedJButton(String name){
this.b = new JButton(name);
}
public void addActionListener(ActionListener l) {
b.addActionListener(l);
}
}
public final WrappedJButton next = new WrappedJButton("Next");
public final WrappedJButton prev = new WrappedJButton("Previous");
public void setup() {
JPanel buttons = new JPanel();
buttons.setLayout(new FlowLayout());
buttons.add(previous.b);
buttons.add(next.b);
}
} // end view
class Presenter {
public Presenter() {
View view = new View();
view.next.addActionListener(event -> {
// Respond to button push
});
}
} // end Presenter
This wrapper works well. Making the wrapped buttons public allows the Presenter to reference them by name (which allows my IDE to use code completion); but, because they are WrappedJButton objects, the only thing the Presenter can do is add an ActionListener. The View can get "full" access to the objects by grabbing the "real" button through the private b field.
Questions:
Is there an even better/cleaner solution? Perhaps something that
would eliminate the need to access the b field in the View?
Is there a way to generalize this solution so I don't have to
cut-and-paste WrappedJButton into every View class I write? I
tried moving WrappedJButton into an interface (which View
implements); but, when I do that, View no longer has access to the
private b field.
I think it would be ok to avoid copy-pasting the WrapperJButton class by exposing the wrapped buttons on the package level (assuming the the Presenter resides in a different package):
public class WrappedJButton {
final JButton b;
WrappedJButton(String name){
this.b = new JButton(name);
}
public void addActionListener(ActionListener l) {
b.addActionListener(l);
}
}
A different approach could be to store the buttons in a map:
class ButtonMap<E extends Enum<E>> {
private final EnumMap<E, JButton> map;
ButtonMap(Class<E> buttonEnum){
map = new EnumMap<>(buttonEnum);
for(E e : buttonEnum.getEnumConstants()){
map.put(e, new JButton(e.toString()));
}
}
JButton get(E e){
return map.get(e);
}
}
a view using this map could look like this:
public class View {
private final ButtonMap<ViewButton> buttonMap = new ButtonMap<>(ViewButton.class);
public enum ViewButton{
NEXT("Next"),
PREV("Prev");
private final String name;
private ViewButton(String name){
this.name = name;
}
#Override
public String toString(){
return name;
}
}
public void setup() {
JPanel buttons = new JPanel();
buttons.setLayout(new FlowLayout());
buttons.add(buttonMap.get(ViewButton.PREV));
buttons.add(buttonMap.get(ViewButton.NEXT));
}
public void addActionListener(ViewButton button, ActionListener l){
buttonMap.get(button).addActionListener(l);
}
} // end view
The button map is hidden with a private field. Only the addActionListener method of the buttons is exposed.
I am new to Vaadin and trying to know if it can suit my needs for a webapp project migration.
Actually I'm already loosing my time on a simple goal: to have a layout with fixed headers and footers, and a scrollable content in the middle.
I made a very basic fiddle with what I want:
jsfiddle
Here is the main Vaadin class I came up with:
public class MyVaadinUI extends UI {
// attributes
#WebServlet(value = "/*", asyncSupported = true)
#VaadinServletConfiguration(productionMode = false, ui = MyVaadinUI.class, widgetset = "testvaadin.aep.com.AppWidgetSet")
public static class Servlet extends VaadinServlet {
}
#Override
protected void init(VaadinRequest request) {
buildMainLayout();
}
private void buildMainLayout() {
final VerticalLayout mainLayout = new VerticalLayout();
mainLayout.setSizeFull();
//HEADER
final VerticalLayout headerLayout = new VerticalLayout();
final Resource res = new ThemeResource("img/logo.png");
final Image image = new Image(null, res);
headerLayout.addComponent(image);
//CONTENT
final VerticalLayout contentLayout = new VerticalLayout();
for(int i=0; i<80; i++){
contentLayout.addComponent(new Button("TEST " + i));
}
//FOOTER
final VerticalLayout footerLayout = new VerticalLayout();
footerLayout.addComponent(new Label("--------------------------- footer --------------------------"));
mainLayout.addComponent(headerLayout);
mainLayout.addComponent(contentLayout);
mainLayout.addComponent(footerLayout);
mainLayout.setExpandRatio(contentLayout, 1);
setContent(mainLayout);
}
}
The displayed page is OK on startup, but when I scroll down, the footer also scrolls (it is not fixed).
On startup:
When scrolled:
I browsed a lot of pages on this topic, but I did never see any correct answer. This seems to be rather complicated in Vaadin, although it is very simple in HTML; Vaadin may not suit my needs.
Anyway, do you know how can I achieve this behaviour?
Thanks!
You can use a Panel to create a scrollable center content area. See the example below.
For the panel to work, everything in the component hierarchy must be setSizeFull (or equivalent) and the content of the panel must not (in the example mainLayout and contentPanel are 100%, but contentLayout is not (implicit))
#Grapes([
#Grab('org.vaadin.spring:spring-boot-vaadin:0.0.3'),
#Grab('com.vaadin:vaadin-client-compiled:7.4.0.beta1'),
#Grab('com.vaadin:vaadin-themes:7.4.0.beta1'),
])
import com.vaadin.ui.*
#org.vaadin.spring.VaadinUI
class MyUI extends UI {
protected void init(com.vaadin.server.VaadinRequest request) {
final headerLayout = new VerticalLayout(new Label('HEADER'))
final footerLayout = new VerticalLayout(new Label('FOOTER'))
final contentLayout = new VerticalLayout()
80.times{ contentLayout.addComponent(new Button("TEST $it")) }
// XXX: place the center layout into a panel, which allows scrollbars
final contentPanel = new Panel(contentLayout)
contentPanel.setSizeFull()
// XXX: add the panel instead of the layout
final mainLayout = new VerticalLayout(headerLayout, contentPanel, footerLayout)
mainLayout.setSizeFull()
mainLayout.setExpandRatio(contentPanel, 1)
setContent(mainLayout)
}
}
(runs standalone with spring run vaadin.groovy)
I have 2 classes.
when I put bold 3 lines in the method addCourses() the dialog does not show combobox in the Panel
but when I remove from addCourses and put those bold lines in the constructor, JComboBox are shown in the Panel.
But data will not show because data items updates to ComboBox will happen after Constructor is created.
How can I solve this problem.
this.mainPanel.add(courseCombo, BorderLayout.NORTH);
this.mainPanel.add(sessionCombo, BorderLayout.CENTER);
this.mainPanel.add(courseButton, BorderLayout.SOUTH);
public class Updator {
CourseListFrame clf = new CourseListFrame();
for(...){
clf.addContentsToBox(displayName, className);
}
clf.addCourses();
}
and second class is
public class CourseListFrame extends JDialog implements ActionListener {
public JPanel mainPanel = new JPanel(new BorderLayout(2, 2));
public JButton courseButton = new JButton(("Submit"));
public JComboBox courseCombo;
public JComboBox sessionCombo;
public Multimap<String, String> map; // = HashMultimap.create();
public static CourseListFrame courseListDialog;
public CourseListFrame() {
super(this.getMainFrame());
this.getContentPane().add(mainPanel);
map = HashMultimap.create();
courseCombo = new JComboBox();
courseCombo.addItem("Select Courses");
courseCombo.addActionListener(this);
sessionCombo = new JComboBox();
}
public void addContentsToBox(String course, String session) {
map.put(course, session);
courseCombo.addItem(course);
}
public void actionPerformed(ActionEvent e) {
JComboBox cb = (JComboBox) e.getSource();
String str = (String) cb.getSelectedItem();
setSessionCombo(str);
}
public void setSessionCombo(String course) {
if (map.containsKey(course)) {
sessionCombo.removeAllItems();
Iterator it = map.get(course).iterator();
while (it.hasNext()) {
sessionCombo.addItem(it.next());
}
}
}
public void addCourses() {
this.mainPanel.add(courseCombo, BorderLayout.NORTH);
this.mainPanel.add(sessionCombo, BorderLayout.CENTER);
this.mainPanel.add(courseButton, BorderLayout.SOUTH);
}
public static void showCourseListDialog() {
if (courseListDialog == null) {
courseListDialog = new CourseListFrame();
}
courseListDialog.pack();
courseListDialog.setVisible(true);
courseListDialog.setSize(260, 180);
}
}
The reason why they arent showing is because you are probably calling the static showCourseListDialog() to show your dialog. This method will test whether your static courseListDialog is null, and if so, create one and set that dialog visible, not the clf that you instantiated.
If in your showCourseListDialog() you call the addCourses() method after instantiating your 'singleton', you should be OK:
public static void showCourseListDialog() {
if (courseListDialog == null) {
courseListDialog = new CourseListFrame();
courseListDialog.addCourses();// <<---- this is key!
}
courseListDialog.pack();
courseListDialog.setVisible(true);
courseListDialog.setSize(260, 180);
}
That said, by having the static courseListDialog, it is apparent that you want that dialog to be a singleton. If that is the case, I would at least make your constructor private. You want to proactively avoid the situation that you are getting into where you can construct multiple instances of a singleton. You still would have a race condition to deal with in your showCourseListDialog, but as you will only be calling this method in the EDT, you should be safe.
Take a look at this and other topics on Singleton development in Java (and dont forget to read the con arguments where it is described as an anti-pattern)