Splash-Screen and Threads


Today I thought it would make my application look much more professional if the login-dialog is part of the splash-screen. The new extension point added in eclipse makes this possible and even provided an template implementation I could use without problem.

The process is straight forward:

  • Create a Product Configuration (or use your existing one)
  • Switch to the Splash-Tab
  • Select in the Customization-Section the Interactive-Template
  • Switch to the Overview and “Launch an Eclipse application”

You are done. That was easy, wasn’t it? To make this work you wouldn’t need any help. The tricky thing I faced starts now. In my case I’m authentificating using a database-server and to not block the UI im doing this in a seperate thread and showing a ProgressBar in the mean while.

When the application starts up the splash should look like this:

And while Logging into Database:

So the first action is to add the “Check Login”-Label and the Progress bar like this:

private Label progressLabel;
private ProgressBar progressBar;

/** many lines of code */

private void createProgressInfo() {
  progressLabel = new Label(fCompositeLogin,SWT.NONE);
  progressLabel.setText("Überprüfung läuft");
  GridData data = new GridData();
  data.horizontalIndent = F_LABEL_HORIZONTAL_INDENT - 50;
  progressLabel.setLayoutData(data);
  progressLabel.setVisible(false);

  progressBar = new ProgressBar(fCompositeLogin,SWT.NONE|SWT.INDETERMINATE);
  data = new GridData(SWT.NONE, SWT.NONE, false, false);
  data.widthHint = F_TEXT_WIDTH_HINT;
  data.horizontalSpan = 2;
  progressBar.setLayoutData(data);
  progressBar.setVisible(false);
}

private void toggelCheckProgress(boolean state) {
  progressLabel.setVisible(state);
  progressBar.setVisible(state);
  fCompositeLogin.layout();
}

We initially set the those two widgets invisible and show them later when we start the authentification. To make this easy we add helper method to turn the visibility on and off.

The next part is to modify the handleButtonOKWidgetSelected()-method like this:

private volatile int loginStatus = -1;

/** many lines of code */

private void handleButtonOKWidgetSelected() {
  final String username = fTextUsername.getText();
  final String password = fTextPassword.getText();

  toggelCheckProgress(true);
  Thread t = new Thread() {
    public void run() {
      if( login(username,password) ) {
        loginStatus = 1;
      } else {
        loginStatus = 2;
      }
    }
  }
  t.start();
}

The content of the method is straight forward. It starts a thread and executes a potentially long running task in our case login(String,String). Our task is now to sync back to the gui-thread and:

  1. Proceed with start up (hiding the login details from the splash-screen)
  2. Display Login-Failure to the user

Normally you do this using Display#(a)syncExec() but that’s not available in the splash-screen. The work-around I used as you see above is setting a special variable named loginStatus. The trick is now that you add checks for this variable to the Event-Loop method which looks like this afterwards:

private void doEventLoop() {
  Shell splash = getSplash();
  while (fAuthenticated == false) {
    if (splash.getDisplay().readAndDispatch() == false) {
      if( loginStatus == 1 ) {
        loginSuccess();
      } else if( loginStatus == 2 ) {
        loginFailure();
      }

      splash.getDisplay().sleep();
    }
  }
}

Your are nearly done now the only two methods missing are:

private void loginSuccess() {
  toggelCheckProgress(false);
  fCompositeLogin.setVisible(false);
  fAuthenticated = true;
  loginStatus = -1;
}

private void loginFailure() {
  toggelCheckProgress(false);
  loginStatus = -1;
  MessageDialog.openError(getSplash(),"Authentification failed","Your username or password was wrong");
}

Well that’s all. You are done and have a splash screen who is authenticating without blocking the UI-thread without Display#(a)syncExec. I need to thank Kim for pointing me to this solution and she promised that there will be added API in 3.4 to make this more easier.

About these ads

16 Responses to “Splash-Screen and Threads”

  1. What look and feeel is that

  2. MacOSX with ShapeShifter and Aluminum Alloy 1.6.1

  3. that’s awesome!

  4. One more note: The code Thread-code has to be modified slightly to force the event-loop to run:

    public void run() {
    // As before
    getSplash().getDisplay().wake();
    }

  5. Great! Only thing:
    Is there a more detailed workeround for this solution? For instance which classes the methods have to be implemented in is unclear for me.

  6. 1. Screen:
    public class InteractiveSplashHandler extends LoginSplashHandler {

    }

    2. Real implementation:

    import org.eclipse.jface.dialogs.MessageDialog;
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.events.SelectionAdapter;
    import org.eclipse.swt.events.SelectionEvent;
    import org.eclipse.swt.events.TraverseEvent;
    import org.eclipse.swt.events.TraverseListener;
    import org.eclipse.swt.layout.FillLayout;
    import org.eclipse.swt.layout.GridData;
    import org.eclipse.swt.layout.GridLayout;
    import org.eclipse.swt.widgets.Button;
    import org.eclipse.swt.widgets.Composite;
    import org.eclipse.swt.widgets.Label;
    import org.eclipse.swt.widgets.ProgressBar;
    import org.eclipse.swt.widgets.Shell;
    import org.eclipse.swt.widgets.Text;
    import org.eclipse.ui.splash.AbstractSplashHandler;

    import at.bestsolution.swtforms.util.AbstractSwtFormsConfiguration;
    import at.bestsolution.swtforms.util.DefaultUser;
    import at.bestsolution.swtforms.util.SwtFormsGlobalStorage;

    /**
    * @since 3.3
    *
    */
    public class LoginSplashHandler extends AbstractSplashHandler {

    private final static int F_LABEL_HORIZONTAL_INDENT = 205;

    private final static int F_BUTTON_WIDTH_HINT = 80;

    private final static int F_TEXT_WIDTH_HINT = 165;

    private final static int F_COLUMN_COUNT = 3;

    private Composite fCompositeLogin;

    private Text fTextUsername;

    private Text fTextPassword;

    private Button fButtonOK;

    private Button fButtonCancel;

    private boolean fAuthenticated;

    private Label progressLabel;

    private ProgressBar progressBar;

    private volatile int loginStatus = -1;

    /**
    *
    */
    public LoginSplashHandler() {
    fCompositeLogin = null;
    fTextUsername = null;
    fTextPassword = null;
    fButtonOK = null;
    fButtonCancel = null;
    fAuthenticated = false;
    }

    /*
    * (non-Javadoc)
    *
    * @see org.eclipse.ui.splash.AbstractSplashHandler#init(org.eclipse.swt.widgets.Shell)
    */
    public void init(final Shell splash) {
    // Store the shell
    super.init(splash);
    // Configure the shell layout
    configureUISplash();
    // Create UI
    createUI();
    // Create UI listeners
    createUIListeners();
    // Force the splash screen to layout
    splash.layout(true);
    // Keep the splash screen visible and prevent the RCP application from
    // loading until the close button is clicked.
    doEventLoop();
    }

    /**
    *
    */
    private void doEventLoop() {
    Shell splash = getSplash();
    while (fAuthenticated == false) {
    if (splash.getDisplay().readAndDispatch() == false) {
    if( loginStatus == 1 ) {
    loginSuccess();
    } else if( loginStatus == 2 ) {
    loginFailure();
    }

    splash.getDisplay().sleep();
    }
    }
    }

    private void loginSuccess() {
    toggelCheckProgress(false);
    fCompositeLogin.setVisible(false);
    fAuthenticated = true;
    loginStatus = -1;
    }

    private void loginFailure() {
    toggelCheckProgress(false);
    loginStatus = -1;
    MessageDialog.openError(
    getSplash(),
    “Anmeldung fehlgeschlagen”, //NON-NLS-1
    “Benutzer und/oder Passwort falsch”); //NON-NLS-1
    }

    /**
    *
    */
    private void createUIListeners() {
    // Create the OK button listeners
    createUIListenersButtonOK();
    // Create the cancel button listeners
    createUIListenersButtonCancel();
    // Create listener to press ok on return
    createUIListenersReturnOK();
    }

    private void createUIListenersReturnOK() {
    fTextPassword.addTraverseListener(new TraverseListener() {

    public void keyTraversed(TraverseEvent e) {
    if( e.detail == SWT.TRAVERSE_RETURN ) {
    handleButtonOKWidgetSelected();
    }
    }

    });
    }

    /**
    *
    */
    private void createUIListenersButtonCancel() {
    fButtonCancel.addSelectionListener(new SelectionAdapter() {
    public void widgetSelected(SelectionEvent e) {
    handleButtonCancelWidgetSelected();
    }
    });
    }

    /**
    *
    */
    private void handleButtonCancelWidgetSelected() {
    // Abort the loading of the RCP application
    getSplash().getDisplay().close();
    System.exit(0);
    }

    /**
    *
    */
    private void createUIListenersButtonOK() {
    fButtonOK.addSelectionListener(new SelectionAdapter() {
    public void widgetSelected(SelectionEvent e) {
    handleButtonOKWidgetSelected();
    }
    });
    }

    /**
    *
    */
    private void handleButtonOKWidgetSelected() {
    final String username = fTextUsername.getText();
    final String password = fTextPassword.getText();

    toggelCheckProgress(true);

    Thread t = new Thread() {

    @Override
    public void run() {
    DefaultUser user = new DefaultUser(username,password);
    if( user.login() ) {
    AbstractSwtFormsConfiguration config = SwtFormsGlobalStorage.getInstance().getAppSpecificDataProvider().getConfiguration();
    config.setProperty(“swtforms.login.lastUser”, username );
    try {
    config.save();
    } catch (Exception e) {
    e.printStackTrace();
    }
    loginStatus = 1;
    } else {
    loginStatus = 2;
    }
    getSplash().getDisplay().wake();
    }

    };
    t.start();
    }

    /**
    *
    */
    private void createUI() {
    // Create the login panel
    createUICompositeLogin();
    // Create the blank spanner
    createUICompositeBlank();
    // Create the user name label
    createUILabelUserName();
    // Create the user name text widget
    createUITextUserName();
    // Create the password label
    createUILabelPassword();
    // Create the password text widget
    createUITextPassword();
    // Create the blank label
    createUILabelBlank();
    // Create the OK button
    createUIButtonOK();
    // Create the cancel button
    createUIButtonCancel();
    // Create progress
    createProgressInfo();
    }

    private void createProgressInfo() {
    progressLabel = new Label(fCompositeLogin,SWT.NONE);
    progressLabel.setText(“Überprüfung läuft”);
    GridData data = new GridData();
    data.horizontalIndent = F_LABEL_HORIZONTAL_INDENT – 50;
    progressLabel.setLayoutData(data);
    progressLabel.setVisible(false);

    progressBar = new ProgressBar(fCompositeLogin,SWT.NONE|SWT.INDETERMINATE);
    data = new GridData(SWT.NONE, SWT.NONE, false, false);
    data.widthHint = F_TEXT_WIDTH_HINT;
    data.horizontalSpan = 2;
    progressBar.setLayoutData(data);
    progressBar.setVisible(false);
    }

    private void toggelCheckProgress(boolean state) {
    progressLabel.setVisible(state);
    progressBar.setVisible(state);
    fCompositeLogin.layout();
    }

    /**
    *
    */
    private void createUIButtonCancel() {
    // Create the button
    fButtonCancel = new Button(fCompositeLogin, SWT.PUSH);
    fButtonCancel.setText(“Cancel”); //NON-NLS-1
    // Configure layout data
    GridData data = new GridData(SWT.NONE, SWT.NONE, false, false);
    data.widthHint = F_BUTTON_WIDTH_HINT;
    fButtonCancel.setLayoutData(data);
    }

    /**
    *
    */
    private void createUIButtonOK() {
    // Create the button
    fButtonOK = new Button(fCompositeLogin, SWT.PUSH);
    fButtonOK.setText(“OK”); //NON-NLS-1
    // Configure layout data
    GridData data = new GridData(SWT.NONE, SWT.NONE, false, false);
    data.widthHint = F_BUTTON_WIDTH_HINT;
    fButtonOK.setLayoutData(data);
    }

    /**
    *
    */
    private void createUILabelBlank() {
    Label label = new Label(fCompositeLogin, SWT.NONE);
    label.setVisible(false);
    }

    /**
    *
    */
    private void createUITextPassword() {
    // Create the text widget
    int style = SWT.PASSWORD | SWT.BORDER;
    fTextPassword = new Text(fCompositeLogin, style);

    String password = getPassword();

    if( password != null ) {
    fTextPassword.setText(password);
    }

    // Configure layout data
    GridData data = new GridData(SWT.NONE, SWT.NONE, false, false);
    data.widthHint = F_TEXT_WIDTH_HINT;
    data.horizontalSpan = 2;
    fTextPassword.setLayoutData(data);
    }

    /**
    *
    */
    private void createUILabelPassword() {
    // Create the label
    Label label = new Label(fCompositeLogin, SWT.NONE);
    label.setText(“&Passwort:”); //NON-NLS-1
    // Configure layout data
    GridData data = new GridData();
    data.horizontalIndent = F_LABEL_HORIZONTAL_INDENT;
    label.setLayoutData(data);
    }

    /**
    *
    */
    private void createUITextUserName() {
    // Create the text widget
    fTextUsername = new Text(fCompositeLogin, SWT.BORDER);
    String username = getUsername();

    if( username != null ) {
    fTextUsername.setText(username);
    }

    // Configure layout data
    GridData data = new GridData(SWT.NONE, SWT.NONE, false, false);
    data.widthHint = F_TEXT_WIDTH_HINT;
    data.horizontalSpan = 2;
    fTextUsername.setLayoutData(data);
    }

    /**
    *
    */
    private void createUILabelUserName() {
    // Create the label
    Label label = new Label(fCompositeLogin, SWT.NONE);
    label.setText(“&Benutzer:”); //NON-NLS-1
    // Configure layout data
    GridData data = new GridData();
    data.horizontalIndent = F_LABEL_HORIZONTAL_INDENT;
    label.setLayoutData(data);
    }

    /**
    *
    */
    private void createUICompositeBlank() {
    Composite spanner = new Composite(fCompositeLogin, SWT.NONE);
    GridData data = new GridData(SWT.FILL, SWT.FILL, true, false);
    data.horizontalSpan = F_COLUMN_COUNT;
    data.heightHint=150;
    spanner.setLayoutData(data);
    }

    /**
    *
    */
    private void createUICompositeLogin() {
    // Create the composite
    fCompositeLogin = new Composite(getSplash(), SWT.BORDER);
    GridLayout layout = new GridLayout(F_COLUMN_COUNT, false);
    fCompositeLogin.setLayout(layout);
    }

    /**
    *
    */
    private void configureUISplash() {
    // Configure layout
    FillLayout layout = new FillLayout();
    getSplash().setLayout(layout);
    // Force shell to inherit the splash background
    getSplash().setBackgroundMode(SWT.INHERIT_DEFAULT);
    }

    private static String getUsername() {
    AbstractSwtFormsConfiguration config = SwtFormsGlobalStorage.getInstance().getAppSpecificDataProvider().getConfiguration();
    if( config.getLoadLastUsername() ) {
    return config.getProperty(“swtforms.login.lastUser”);
    } else {
    return null;
    }
    }

    private static String getPassword() {
    AbstractSwtFormsConfiguration config = SwtFormsGlobalStorage.getInstance().getAppSpecificDataProvider().getConfiguration();
    if( config.getLoadPassword() ) {
    return config.getProperty(“swtforms.login.lastPwd”);
    } else {
    return null;
    }
    }
    }

  7. First of all: Thanks for this very nice Feature!!!

    I switched from a Login Dialog to the Splash Login with your Improvement and it works great!

    The only (little) thing that’s missing to make the Splash Login perfect is to see the splash in the (Windows) Task Bar. Without that you can easily “loose” the Splash-Screen among the other open Windows…

    Do you (or anybody else) also have any Idea how to solve this Problem?

  8. There’s a resent thread on RCP-Platform asking for this kind of feature and the answer was from Andrew Niefer was:


    The splash screen window is created natively with the extended window style WS_EX_TOOLWINDOW which makes it not appear in the task bar. This corresponds to the SWT constant SWT.TOOL.

    I don’t know if it is possible to change this afterwards. That native window is wrapped in the SWT.Shell that contains your interactive splash, so you may be able to change it there.

    You may also be able to create a new shell that doesn’t have this style (Nest it inside the splash shell maybe?) to force it to show in the task bar.

  9. thanks for your fast reply!

    I also read this thread and tried the second idea of Andrew but that didn’t work (maybe I haven’t done it all correct) Now i’m trying to apply the first alternative but that looks to me like I had to change some internal eclipse Code what I don’t want. I thaught there could be an easy way but I think that’s not so trivial.

    I’ll give it another try today (and reply here and in the RCP-Platform Thread if I find a solution)

  10. Hi Tom,
    Thanks for the excelent post.

    As I am new to SWT, would it be possible for you to post the given util classes somewhere?

    Thanks anyway.

    Marcelo

  11. Thanks for the example.
    Iam new to SWt and Java, would possible to have the complte code example for an application ?

    Thanks.

    • The Splash-Template will generate the base code parts for you. The only thing you need to do is to apply the changes I’ve shown here to this code.

  12. Nice blog and Great article!
    What would you recommend if I would like to implement a logout for my application?
    Thanks.

  13. This is not for Eclipse4 RCP 2.0, right?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 599 other followers

%d bloggers like this: