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.
neat stuff! thanks!
I catched a lot of your work, lately! 🙂
That is a ludicrous hack. I LOVE IT!!!
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?
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);
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 🙂
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;
}
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);
}
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);
}
Nice hack! Unfortunately the checkbox gets a grey background on OS X instead of the blue or white table row background color.
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);
}
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);
It works ok. 🙂
Thanks for sharing this trick.
nice post
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.
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?