TableViewers and Nativelooking Checkboxes


Many of us have faced the same problem that if you needed to use checkboxes in your TableViewer/TreeViewer they look not native because they are pictures. I had some free minutes and thought that it’s time to create a LabelProvider which is able to create platform look-and-feel images out of the box. It’s not 100% native but it’s not far away. The trick is to automatically create screenshots from CheckBox-Buttons and use them. This way the checkboxes look native on all platforms and correspond to the current look and feel of the platform.

Here’s the code if someone is interested:

package at.bestsolution.jface.viewers;

import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Shell;

public abstract class EmulatedNativeCheckBoxLabelProvider extends ColumnLabelProvider {
  private static final String CHECKED_KEY = "CHECKED";
  private static final String UNCHECK_KEY = "UNCHECKED";

  public EmulatedNativeCheckBoxLabelProvider(ColumnViewer viewer) {
    if( JFaceResources.getImageRegistry().getDescriptor(CHECKED_KEY) == null ) {
      JFaceResources.getImageRegistry().put(UNCHECK_KEY, makeShot(viewer.getControl().getShell(),false));
      JFaceResources.getImageRegistry().put(CHECKED_KEY, makeShot(viewer.getControl().getShell(),true));
    }
  }

  private Image makeShot(Shell shell, boolean type) {
    Shell s = new Shell(shell,SWT.NO_TRIM);
    Button b = new Button(s,SWT.CHECK);
    b.setSelection(type);
    Point bsize = b.computeSize(SWT.DEFAULT, SWT.DEFAULT);
    b.setSize(bsize);
    b.setLocation(0, 0);
    s.setSize(bsize);
    s.open();

    GC gc = new GC(b);
    Image image = new Image(shell.getDisplay(), bsize.x, bsize.y);
    gc.copyArea(image, 0, 0);
    gc.dispose();

    s.close();

    return image;
  }

  public Image getImage(Object element) {
    if( isChecked(element) ) {
      return JFaceResources.getImageRegistry().getDescriptor(CHECKED_KEY).createImage();
    } else {
      return JFaceResources.getImageRegistry().getDescriptor(UNCHECK_KEY).createImage();
    }
  }

  protected abstract boolean isChecked(Object element);
}

I haven’t really tested this (currently only on WinXP) but I suppose it’s working on all platforms. You can also get the code from my svn-repository which holds some other interesting utilities and viewer classes.

Advertisements
This entry was posted in 3.x. Bookmark the permalink.

15 Responses to TableViewers and Nativelooking Checkboxes

  1. andre says:

    neat stuff! thanks!
    I catched a lot of your work, lately! 🙂

  2. Fellow RCP Coder says:

    That is a ludicrous hack. I LOVE IT!!!

  3. Anonymous says:

    However, you are able to add native (not native like) check boxes on the Tree/Table widget, which TreeViewer /TableViewer wraps.

    Why can’t the SWT.CHECK flag be pushed somehow into the underlying widget?

  4. Anonymous says:

    back with some more light on the mater. Here’s a bit of code from the last JFace:

    /**
    * Creates a tree viewer on a newly-created tree control under the given
    * parent. The tree control is created using the given SWT style bits. The
    * viewer has no input, no content provider, a default label provider, no
    * sorter, and no filters.
    *
    * @param parent
    * the parent control
    * @param style
    * the SWT style bits used to create the tree.
    */
    public TreeViewer(Composite parent, int style) {
    this(new Tree(parent, style));
    }

    The style you specify here is passed unchanged to the inner Tree widget.

    This worked perfectly for me:

    TreeViewer viewer = new TreeViewer(viewerParent, SWT.CHECK);

  5. Tom says:

    I know SWT.CHECK but the problem is that with this you can only have a checkbox in the first column of your Table/Tree. This solution works in any column of a Viewer 🙂

  6. Anonymous says:

    private Image makeShot(Control control, boolean type) {
    Shell s = new Shell(control.getShell(), SWT.NO_TRIM);
    // otherwise we have a default gray color
    Color backgroundColor = control.getBackground();
    s.setBackground(backgroundColor);
    Button b = new Button(s, SWT.CHECK);
    b.setBackground(backgroundColor);
    b.setSelection(type);
    // otherwise an image is located in a corner
    b.setLocation(1, 1);
    Point bsize = b.computeSize(SWT.DEFAULT, SWT.DEFAULT);
    // otherwise an image is stretched by width
    bsize.x=Math.max(bsize.x, bsize.y);
    bsize.y=Math.max(bsize.x, bsize.y);
    b.setSize(bsize);
    s.setSize(bsize);
    s.open();
    GC gc = new GC(s);
    Image image = new Image(control.getDisplay(), bsize.x, bsize.y);
    gc.copyArea(image, 0, 0);
    gc.dispose();
    s.close();
    return image;
    }

  7. Anonymous says:

    here the fully tested and working code. improvements:

    * checkbox is painted at the right position
    * the correct beackground-color is used
    * bugfix: the previous getImage() created a new image at every getImage()-call. this led to a NoMoreHandles-Exception

    package de.fhmracing.glasseye.canexplorer.gui.transmit;

    import org.eclipse.jface.resource.JFaceResources;
    import org.eclipse.jface.viewers.ColumnLabelProvider;
    import org.eclipse.jface.viewers.ColumnViewer;
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.graphics.Color;
    import org.eclipse.swt.graphics.GC;
    import org.eclipse.swt.graphics.Image;
    import org.eclipse.swt.graphics.Point;
    import org.eclipse.swt.widgets.Button;
    import org.eclipse.swt.widgets.Control;
    import org.eclipse.swt.widgets.Shell;

    public abstract class EmulatedNativeCheckBoxLabelProvider extends ColumnLabelProvider
    {
    private static final String CHECKED_KEY = “CHECKED”;
    private static final String UNCHECK_KEY = “UNCHECKED”;

    public EmulatedNativeCheckBoxLabelProvider(ColumnViewer viewer)
    {
    if (JFaceResources.getImageRegistry().getDescriptor(CHECKED_KEY) == null)
    {
    JFaceResources.getImageRegistry().put(UNCHECK_KEY, makeShot(viewer.getControl(), false));
    JFaceResources.getImageRegistry().put(CHECKED_KEY, makeShot(viewer.getControl(), true));
    }
    }

    private Image makeShot(Control control, boolean type)
    {
    Shell shell = new Shell(control.getShell(), SWT.NO_TRIM);

    // otherwise we have a default gray color
    Color backgroundColor = control.getBackground();
    shell.setBackground(backgroundColor);

    Button button = new Button(shell, SWT.CHECK);
    button.setBackground(backgroundColor);
    button.setSelection(type);

    // otherwise an image is located in a corner
    button.setLocation(1, 1);
    Point bsize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT);

    // otherwise an image is stretched by width
    bsize.x = Math.max(bsize.x-1, bsize.y-1);
    bsize.y = Math.max(bsize.x-1, bsize.y-1);
    button.setSize(bsize);
    shell.setSize(bsize);

    shell.open();
    GC gc = new GC(shell);
    Image image = new Image(control.getDisplay(), bsize.x, bsize.y);
    gc.copyArea(image, 0, 0);
    gc.dispose();
    shell.close();

    return image;
    }

    public Image getImage(Object element)
    {
    if (isChecked(element))
    {
    return JFaceResources.getImageRegistry().get(CHECKED_KEY);
    }
    else
    {
    return JFaceResources.getImageRegistry().get(UNCHECK_KEY);
    }
    }

    protected abstract boolean isChecked(Object element);
    }

  8. Florian Potschka says:

    here the fully tested and working code. improvements:

    * checkbox is painted at the right position
    * the correct beackground-color is used
    * bugfix: the previous getImage() created a new image at every getImage()-call. this led to a NoMoreHandles-Exception

    package de.fhmracing.glasseye.canexplorer.gui.transmit;

    import org.eclipse.jface.resource.JFaceResources;
    import org.eclipse.jface.viewers.ColumnLabelProvider;
    import org.eclipse.jface.viewers.ColumnViewer;
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.graphics.Color;
    import org.eclipse.swt.graphics.GC;
    import org.eclipse.swt.graphics.Image;
    import org.eclipse.swt.graphics.Point;
    import org.eclipse.swt.widgets.Button;
    import org.eclipse.swt.widgets.Control;
    import org.eclipse.swt.widgets.Shell;

    public abstract class EmulatedNativeCheckBoxLabelProvider extends ColumnLabelProvider
    {
    private static final String CHECKED_KEY = “CHECKED”;
    private static final String UNCHECK_KEY = “UNCHECKED”;

    public EmulatedNativeCheckBoxLabelProvider(ColumnViewer viewer)
    {
    if (JFaceResources.getImageRegistry().getDescriptor(CHECKED_KEY) == null)
    {
    JFaceResources.getImageRegistry().put(UNCHECK_KEY, makeShot(viewer.getControl(), false));
    JFaceResources.getImageRegistry().put(CHECKED_KEY, makeShot(viewer.getControl(), true));
    }
    }

    private Image makeShot(Control control, boolean type)
    {
    Shell shell = new Shell(control.getShell(), SWT.NO_TRIM);

    // otherwise we have a default gray color
    Color backgroundColor = control.getBackground();
    shell.setBackground(backgroundColor);

    Button button = new Button(shell, SWT.CHECK);
    button.setBackground(backgroundColor);
    button.setSelection(type);

    // otherwise an image is located in a corner
    button.setLocation(1, 1);
    Point bsize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT);

    // otherwise an image is stretched by width
    bsize.x = Math.max(bsize.x-1, bsize.y-1);
    bsize.y = Math.max(bsize.x-1, bsize.y-1);
    button.setSize(bsize);
    shell.setSize(bsize);

    shell.open();
    GC gc = new GC(shell);
    Image image = new Image(control.getDisplay(), bsize.x, bsize.y);
    gc.copyArea(image, 0, 0);
    gc.dispose();
    shell.close();

    return image;
    }

    public Image getImage(Object element)
    {
    if (isChecked(element))
    {
    return JFaceResources.getImageRegistry().get(CHECKED_KEY);
    }
    else
    {
    return JFaceResources.getImageRegistry().get(UNCHECK_KEY);
    }
    }

    protected abstract boolean isChecked(Object element);
    }

  9. Alexander Ljungberg says:

    Nice hack! Unfortunately the checkbox gets a grey background on OS X instead of the blue or white table row background color.

  10. Alexander Ljungberg says:

    I came up with a a workaround that works for me on the Mac. I haven’t tested it on Windows but I assume it’d work there too.

    package de.fhmracing.glasseye.canexplorer.gui.transmit;

    import org.eclipse.jface.resource.JFaceResources;
    import org.eclipse.jface.viewers.ColumnLabelProvider;
    import org.eclipse.jface.viewers.ColumnViewer;
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.graphics.Color;
    import org.eclipse.swt.graphics.GC;
    import org.eclipse.swt.graphics.Image;
    import org.eclipse.swt.graphics.Point;
    import org.eclipse.swt.widgets.Button;
    import org.eclipse.swt.widgets.Control;
    import org.eclipse.swt.widgets.Shell;

    public abstract class EmulatedNativeCheckBoxLabelProvider extends
    ColumnLabelProvider {
    private static final String CHECKED_KEY = “CHECKED”;
    private static final String UNCHECK_KEY = “UNCHECKED”;

    public EmulatedNativeCheckBoxLabelProvider(ColumnViewer viewer) {
    if (JFaceResources.getImageRegistry().getDescriptor(CHECKED_KEY) == null) {
    JFaceResources.getImageRegistry().put(UNCHECK_KEY,
    makeShot(viewer.getControl(), false));
    JFaceResources.getImageRegistry().put(CHECKED_KEY,
    makeShot(viewer.getControl(), true));
    }
    }

    private Image makeShot(Control control, boolean type)
    {
    // Hopefully no platform uses exactly this color because we’ll make
    // it transparent in the image.
    Color greenScreen = new Color(control.getDisplay(), 222, 223, 224);

    Shell shell = new Shell(control.getShell(), SWT.NO_TRIM);

    // otherwise we have a default gray color
    shell.setBackground(greenScreen);

    Button button = new Button(shell, SWT.CHECK);
    button.setBackground(greenScreen);
    button.setSelection(type);

    // otherwise an image is located in a corner
    button.setLocation(1, 1);
    Point bsize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT);

    // otherwise an image is stretched by width
    bsize.x = Math.max(bsize.x – 1, bsize.y – 1);
    bsize.y = Math.max(bsize.x – 1, bsize.y – 1);
    button.setSize(bsize);
    shell.setSize(bsize);

    shell.open();
    GC gc = new GC(shell);
    Image image = new Image(control.getDisplay(), bsize.x, bsize.y);
    gc.copyArea(image, 0, 0);
    gc.dispose();
    shell.close();

    ImageData imageData = image.getImageData();
    imageData.transparentPixel = imageData.palette.getPixel(greenScreen
    .getRGB());

    return new Image(control.getDisplay(), imageData);
    }

    public Image getImage(Object element) {
    if (isChecked(element)) {
    return JFaceResources.getImageRegistry().get(CHECKED_KEY);
    } else {
    return JFaceResources.getImageRegistry().get(UNCHECK_KEY);
    }
    }

    protected abstract boolean isChecked(Object element);
    }

  11. Anonymous says:

    one more thing i guess – if you set the background of the viewer a little border with that color will be visible when the respective row is selected. Setting

    SWT.NO_BACKGROUND

    flag on the Shell and Buttons eliminates the problem:

    Shell shell = new Shell(control.getShell(), SWT.NO_TRIM | SWT.NO_BACKGROUND);

    Button button = new Button(shell, SWT.CHECK | SWT.NO_BACKGROUND);

  12. Explorante says:

    It works ok. 🙂
    Thanks for sharing this trick.

  13. website design New York City says:

    nice post

  14. Thanks for the nice idea. What I do with the background:

    – I pass a reference of the to-be container of the checkbox image (for example treeviewer)
    – in case of image generation I set the background of the to-be container as the background of the shell
    – I save the generated image with a key, which contains the checkbox state (checked or not, etc) and the background color

    This way I will never have any problem with transparent checkboxes. One known limitation is that it does not support background images, but I guess it is impossible to support.

  15. Mark says:

    Late to the party, but I can’t get this to work properly with any of the variants posted here and elsewhere on the web. I wonder if anyone has this working with CentOS GTK, because that’s what I’m using and after several hours trying many things I have no success.

    The problem is that the checkbox does not appear at all, unless I set some text on the checkbox button. It has to be non-empty string and not just white-space. For example, button.setText(“x”). However, even in that case, the checkbox does not show at all when in the unchecked state. It looks normal while checked (except for the “x”), but when unchecked, I see a gray background where the button should be. It seems it doesn’t handle the transparency properly when unchecked and when there is no text on the button.

    Does anyone have any idea how to make this work? Can anyone else confirm that it works for them on CentOS with GTK?

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