Incredible easy question: I have a SWT table (viewer) and use a SWT.MeasureItem listener to set the cell height. How do I align the cell content to the bottom of the cell?
(It would probably work with another listener to SWT.PaintItem and some math and rendering all my cells manually, but that can't be the right way.)
public class TableDialog extends Dialog {
public static void main(String[] args) {
TableDialog dialog = new TableDialog(new Shell());
dialog.open();
}
public TableDialog(Shell parent) {
super(parent);
}
#Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText("Table Test");
newShell.setSize(500, 300);
}
#Override
protected Control createDialogArea(Composite parent) {
Composite container = (Composite) super.createDialogArea(parent);
container.setLayout(new FillLayout());
TableViewer viewer = new TableViewer(container, SWT.BORDER | SWT.FULL_SELECTION);
viewer.setContentProvider(new ArrayContentProvider());
viewer.setInput(Arrays.asList("A", "B", " C"));
Table table = viewer.getTable();
table.setLinesVisible(true);
table.addListener(SWT.MeasureItem, e -> e.height = 90);
return container;
}
}
Once you start using SWT.MeasureItem you need to do the drawing as well.
Since you are using TableViewer you can combine all this in one class by using an OwnerDrawLabelProvider as the viewer label provider. A very simple version would be something like this:
viewer.setLabelProvider(new OwnerDrawLabelProvider()
{
#Override
protected void paint(final Event event, final Object element)
{
String text = element.toString();
GC gc = event.gc;
int textHeight = gc.textExtent(text).y;
int yPos = event.y + event.height - textHeight;
gc.drawText(text, event.x, yPos);
}
#Override
protected void measure(final Event event, final Object element)
{
event.height = 90;
}
#Override
protected void erase(final Event event, final Object element)
{
// Stop the default draw of the foreground
event.detail &= ~SWT.FOREGROUND;
}
});
I am afraid, SWT.PaintItem is the right way in this case.
One of the SWT Snippets demonstrates how to draw multiple lines in a table item. It may serve as a starting point for your custom drawing code:
http://git.eclipse.org/c/platform/eclipse.platform.swt.git/tree/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet231.java
The Custom Drawing Table and Tree Items article provides further information.
Related
I'm trying to add an Eclipse status trimbar contribution where the contents of the contribution (i.e., child elements' text) dynamically change. Note that I'm not trying to add "line, column" information into the status bar.
Ideally, the contribution's width should adapt to the width of its contents, a bit like what happens in VSCode:
However, if that isn't easily feasible, an acceptable alternative would be to have a "fixed" width for the status trimbar, or even a fixed width for each individual child (since I don't intend to have children appearing and disappearing).
Below is what I've tried. How do you think I should do this if nothing I've tried for the past 2 days worked?
In case you think the problem might actually be an Eclipse bug, note that I'm using Eclipse version 2021-06 (4.20.0) on Linux.
What I have tried
I have this in plugin.xml:
<extension
point="org.eclipse.ui.menus">
<menuContribution
locationURI="toolbar:org.eclipse.ui.trim.status">
<toolbar
id="some.package.StatusTrimBarContribution">
<control
class="some.package.StatusTrimBarContribution">
</control>
</toolbar>
</menuContribution>
</extension>
I've tried doing this in at least 4 different ways. As a reference, here is the class StatusTrimBarContribution:
public class StatusTrimBarContribution extends WorkbenchWindowControlContribution {
// A
private static String currText = /* see below */;
private Label m_lbl = null;
private Label m_lbl2 = null;
private Control m_root = null;
public StatusTrimBarContribution() {
// Register to String event
}
#Override
public void dispose() {
// Unregister from String event
}
#Override
protected Control createControl(Composite parent) {
// B
}
#Subscribe // From guava
private void someEventHandler(String event) {
currText = "lp_much_longer";
if (m_lbl != null) {
m_lbl.setText();
m_root.getParent().requestLayout();
}
}
}
Attempt #1
In this attempt we add an intermediary Composite between the labels and parent, we use a GridLayout on the intermediary, and we add a GridData into m_lbl.
// A
private static String currText = "empty";
// B
#Override
protected Control createControl(Composite parent) {
GridLayout gl = new GridLayout(2, false);
Composite comp = new Composite(parent, SWT.NONE);
comp.setLayout(gl);
m_lbl = new Label(comp, SWT.RIGHT);
m_lbl.setText(currText);
GridDataFactory.defaultsFor(m_lbl).hint(200, SWT.DEFAULT).applyTo(m_lbl);
m_lbl2 = new Label(comp, SWT.RIGHT);
m_lbl2.setText("lbl2");
m_root = comp;
return comp;
}
The result is that the labels are too tall and get truncated. Note that I've also tried setting the margin height to 0, and, while it's better than below, the labels would still get truncated.
Attempt #2
Compared to #1, we remove the intermediary Composite, and instead use the layout directly on parent. Also, we remove the GridData, because, at this point, why not.
// A
private static String currText = "empty";
// B
#Override
protected Control createControl(Composite parent) {
GridLayout gl = new GridLayout(2, false);
parent.setLayout(gl);
m_lbl = new Label(parent, SWT.RIGHT);
m_lbl.setText(currText);
m_lbl2 = new Label(parent, SWT.RIGHT);
m_lbl2.setText("lbl2");
m_root = m_lbl;
return m_lbl;
}
The result is the same as before, except, additionally, that m_lbl is not large enough to hold the text:
Attempt #3
Compared to #2, we additionally remove the layout.
// A
private static String currText = "empty";
// B
#Override
protected Control createControl(Composite parent) {
m_lbl = new Label(parent, SWT.RIGHT);
m_lbl.setText(currText);
m_lbl2 = new Label(parent, SWT.RIGHT);
m_lbl2.setText("lbl2");
m_root = m_lbl;
return m_lbl;
}
The labels are no longer too tall, however, the width of the labels is still to small for the text:
Attempt #4
Compared to #3, we initially assign a very long string into m_lbl.
// A
// Yes, this is ridiculous
private static String currText = " ".repeat(60);
// B
#Override
protected Control createControl(Composite parent) {
m_lbl = new Label(parent, SWT.RIGHT);
m_lbl.setText(currText);
m_lbl2 = new Label(parent, SWT.RIGHT);
m_lbl2.setText("lbl2");
m_root = m_lbl;
return m_lbl;
}
The result is that both labels are completely visible, but now m_lbl2 has the same width as m_lbl, which is far more than I want. Plus, if the user's font is different from mine, the width of the labels might become too large or too small due to the initial string.
Using the minimum width setting of GridData works for me:
#Override
protected Control createControl(final Composite parent)
{
final Composite comp = new Composite(parent, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(2).applyTo(comp);
m_lbl = new Label(comp, SWT.RIGHT);
m_lbl.setText("label 1");
GridDataFactory.fillDefaults().grab(true, false).minSize(200, SWT.DEFAULT).applyTo(m_lbl);
m_lbl2 = new Label(comp, SWT.RIGHT);
m_lbl2.setText("lbl2");
GridDataFactory.fillDefaults().grab(true, false).minSize(100, SWT.DEFAULT).applyTo(m_lbl2);
m_root = comp;
return comp;
}
Note: Changing the layout of parent is against the rules, it could damage the layout of other components.
I have a TableView that scrolls vertically, and I would like the ScrollBar to extend to the top of it's parent AnchorPane and to be on top of the filler square at the top right. See below for what it's like by default. Note that my filler node is white, that is not a table column at the top right.
and below this line is what I want, correctly implemented by another program.
I was able to achieve this by doing
Platform.runLater(() ->
{
ScrollBar someScrollBar = (ScrollBar) someTable.lookup(".scroll-bar:vertical");
someScrollBar.setTranslateY(-12);
someScrollBar.setScaleY(1.2);
}
);
where someTable is a TableView made in FXML and is referred to in the controller initialize function.
It looks fine like this, but it doesn't scale correctly. If the containing AnchorPane resizes vertically, it looks awful.
Can anyone suggest a better way to do this?
Thank you so much for your time.
Custom layout of the scrollBars is not supported. And my initial comment you need a custom TableViewSkin with a custom TableHeaderRow: the latter is responsible for managing the .. well, tableHeader is only part of the story, unfortunately.
a TableHeaderRow indeed is responsible for laying out the table header
but: a TableHeaderRow can do nothing to layout the vertical scrollbar - when it tries to do so in an overridden layoutChildren() it's immediately reset
the reset happens in the VirtualFlow (which is the parent of the scrollBar)
So at the end of the day, we need
a custom TableHeaderRow that signals the need for enlarging and relocating the scrollBar: the example below sets a marker if the scrollBar is visible (tbd: check whether or not the menuButton is visible) with the desired additional height in the scrollBar's properties map
a custom VirtualFlow that can handle the marker and actually does the layout as needed: the example below checks for the marker and resizes/relocates the scrollBar if needed.
a custom TableViewSkin to inject both (via overridden factory methods)
The example is written against fx11, should work for fx10 but not for fx9 because the latter doesn't allow to provide a custom VirtualFlow:
public class TableWithoutCorner extends Application {
/**
* Custom TableHeaderRow that requests a larger vbar height
* if needed.
*/
private static class MyTableHeader extends TableHeaderRow {
private Region cornerAlias;
private ScrollBar vBar;
private TableViewSkinBase skin;
public MyTableHeader(TableViewSkinBase skin) {
super(skin);
this.skin = skin;
}
#Override
protected void layoutChildren() {
super.layoutChildren();
adjustCornerLayout();
}
private void adjustCornerLayout() {
checkAlias();
// tbd: check also if corner is visible
if (!vBar.isVisible()) {
vBar.getProperties().remove("DELTA");
} else {
vBar.getProperties().put("DELTA", getHeight());
}
}
private void checkAlias() {
if (cornerAlias == null) {
cornerAlias = (Region) lookup(".show-hide-columns-button");
}
if (vBar == null) {
vBar = (ScrollBar) skin.getSkinnable().lookup(".scroll-bar:vertical");
}
}
}
/**
* Custom VirtualFlow that respects additinal height for its
* vertical ScrollBar.
*/
private static class MyFlow extends VirtualFlow {
private ScrollBar vBar;
private Region clip;
public MyFlow() {
// the scrollbar to adjust
vBar = (ScrollBar) lookup(".scroll-bar:vertical");
// the clipped container to use for accessing viewport dimensions
clip = (Region) lookup(".clipped-container");
}
/**
* Overridden to adjust vertical scrollbar's height and y-location
* after calling super.
*/
#Override
protected void layoutChildren() {
super.layoutChildren();
adjustVBar();
}
/**
* Adjusts vBar height and y-location by the height as
* requested by the table header.
*/
protected void adjustVBar() {
if (vBar.getProperties().get("DELTA") == null) return;
double delta = (double) vBar.getProperties().get("DELTA");
vBar.relocate(clip.getWidth(), - delta);
vBar.resize(vBar.getWidth(), clip.getHeight() + delta);
}
}
/**
* Boilerplate: need custom TableViewSkin to inject a custom TableHeaderRow and
* custom VirtualFlow.
*/
private static class MyTableViewSkin<T> extends TableViewSkin<T> {
public MyTableViewSkin(TableView<T> control) {
super(control);
}
#Override
protected TableHeaderRow createTableHeaderRow() {
return new MyTableHeader(this);
}
#Override
protected VirtualFlow<TableRow<T>> createVirtualFlow() {
return new MyFlow();
}
}
private Parent createContent() {
TableView<Locale> table = new TableView<>(FXCollections.observableArrayList(Locale.getAvailableLocales())) {
#Override
protected Skin<?> createDefaultSkin() {
return new MyTableViewSkin(this);
}
};
TableColumn<Locale, String> col = new TableColumn<>("Name");
col.setCellValueFactory(new PropertyValueFactory<>("displayName"));
table.getColumns().addAll(col);
return table;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
//stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TableWithoutCorner.class.getName());
}
I'm searching for a way to add an overlay over some composites in my application. The overlay will contain an label with text "No data available". The underlying composite need to be shown but the user cannot do anything. My application contains different composite part in one screen so I need a way to only place the overlay over one of the composites. Is there a way to implement this in SWT?
A possible solution would be to put a semi-transparent Shell with no trimmings over the Composite you want to cover.
The tricky part is to update the overlay Shell to continuously match the size, position and visibility of the Composite and its parents (since they also could affect the children bounds and visibility).
So I decided to try to make a class Overlay to do that; it can be used to cover any Control and it uses control and paint listeners to track and match the underlying Control. These listeners are also attached to the whole hierarchy of parents of the Control.
You can set the color, the transparency and a text over the Overlay using the corresponding methods.
I made some simple tests and it seemed to work correctly, but I can't guarantee anything. You might want to give it a try it.
A simple example using it:
public class OverlayTest {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new FillLayout(SWT.VERTICAL));
shell.setSize(250, 250);
// create the composite
Composite composite = new Composite(shell, SWT.NONE);
composite.setLayout(new FillLayout(SWT.VERTICAL));
// add stuff to the composite
for (int i = 0; i < 5; i++) {
new Text(composite, SWT.BORDER).setText("Text " + i);
}
// create the overlay over the composite
Overlay overlay = new Overlay(composite);
overlay.setText("No data available");
// create the button to show/hide the overlay
Button button = new Button(shell, SWT.PUSH);
button.setText("Show/hide overlay");
button.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent arg0) {
// if the overlay is showing we hide it, otherwise we show it
if (overlay.isShowing()) {
overlay.remove();
}
else {
overlay.show();
}
}
});
shell.open();
while (shell != null && !shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
}
And the Overlay class:
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Scrollable;
import org.eclipse.swt.widgets.Shell;
/**
* A customizable overlay over a control.
*
* #author Loris Securo
*/
public class Overlay {
private List<Composite> parents;
private Control objectToOverlay;
private Shell overlay;
private Label label;
private ControlListener controlListener;
private DisposeListener disposeListener;
private PaintListener paintListener;
private boolean showing;
private boolean hasClientArea;
private Scrollable scrollableToOverlay;
public Overlay(Control objectToOverlay) {
Objects.requireNonNull(objectToOverlay);
this.objectToOverlay = objectToOverlay;
// if the object to overlay is an instance of Scrollable (e.g. Shell) then it has
// the getClientArea method, which is preferable over Control.getSize
if (objectToOverlay instanceof Scrollable) {
hasClientArea = true;
scrollableToOverlay = (Scrollable) objectToOverlay;
}
else {
hasClientArea = false;
scrollableToOverlay = null;
}
// save the parents of the object, so we can add/remove listeners to them
parents = new ArrayList<Composite>();
Composite parent = objectToOverlay.getParent();
while (parent != null) {
parents.add(parent);
parent = parent.getParent();
}
// listener to track position and size changes in order to modify the overlay bounds as well
controlListener = new ControlListener() {
#Override
public void controlMoved(ControlEvent e) {
reposition();
}
#Override
public void controlResized(ControlEvent e) {
reposition();
}
};
// listener to track paint changes, like when the object or its parents become not visible (for example changing tab in a TabFolder)
paintListener = new PaintListener() {
#Override
public void paintControl(PaintEvent arg0) {
reposition();
}
};
// listener to remove the overlay if the object to overlay is disposed
disposeListener = new DisposeListener() {
#Override
public void widgetDisposed(DisposeEvent e) {
remove();
}
};
// create the overlay shell
overlay = new Shell(objectToOverlay.getShell(), SWT.NO_TRIM);
// default values of the overlay
overlay.setBackground(objectToOverlay.getDisplay().getSystemColor(SWT.COLOR_GRAY));
overlay.setAlpha(200);
// so the label can inherit the background of the overlay
overlay.setBackgroundMode(SWT.INHERIT_DEFAULT);
// label to display a text
// style WRAP so if it is too long the text get wrapped
label = new Label(overlay, SWT.WRAP);
// to center the label
overlay.setLayout(new GridLayout());
label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
showing = false;
overlay.open();
overlay.setVisible(showing);
}
public void show() {
// if it's already visible we just exit
if (showing) {
return;
}
// set the overlay position over the object
reposition();
// show the overlay
overlay.setVisible(true);
// add listeners to the object to overlay
objectToOverlay.addControlListener(controlListener);
objectToOverlay.addDisposeListener(disposeListener);
objectToOverlay.addPaintListener(paintListener);
// add listeners also to the parents because if they change then also the visibility of our object could change
for (Composite parent : parents) {
parent.addControlListener(controlListener);
parent.addPaintListener(paintListener);
}
showing = true;
}
public void remove() {
// if it's already not visible we just exit
if (!showing) {
return;
}
// remove the listeners
if (!objectToOverlay.isDisposed()) {
objectToOverlay.removeControlListener(controlListener);
objectToOverlay.removeDisposeListener(disposeListener);
objectToOverlay.removePaintListener(paintListener);
}
// remove the parents listeners
for (Composite parent : parents) {
if (!parent.isDisposed()) {
parent.removeControlListener(controlListener);
parent.removePaintListener(paintListener);
}
}
// remove the overlay shell
if (!overlay.isDisposed()) {
overlay.setVisible(false);
}
showing = false;
}
public void setBackground(Color background) {
overlay.setBackground(background);
}
public Color getBackground() {
return overlay.getBackground();
}
public void setAlpha(int alpha) {
overlay.setAlpha(alpha);
}
public int getAlpha() {
return overlay.getAlpha();
}
public boolean isShowing() {
return showing;
}
public void setText(String text) {
label.setText(text);
// to adjust the label size accordingly
overlay.layout();
}
public String getText() {
return label.getText();
}
private void reposition() {
// if the object is not visible, we hide the overlay and exit
if (!objectToOverlay.isVisible()) {
overlay.setBounds(new Rectangle(0, 0, 0, 0));
return;
}
// if the object is visible we need to find the visible region in order to correctly place the overlay
// get the display bounds of the object to overlay
Point objectToOverlayDisplayLocation = objectToOverlay.toDisplay(0, 0);
Point objectToOverlaySize;
// if it has a client area, we prefer that instead of the size
if (hasClientArea) {
Rectangle clientArea = scrollableToOverlay.getClientArea();
objectToOverlaySize = new Point(clientArea.width, clientArea.height);
}
else {
objectToOverlaySize = objectToOverlay.getSize();
}
Rectangle objectToOverlayBounds = new Rectangle(objectToOverlayDisplayLocation.x, objectToOverlayDisplayLocation.y, objectToOverlaySize.x,
objectToOverlaySize.y);
Rectangle intersection = objectToOverlayBounds;
// intersect the bounds of the object with its parents bounds so we get only the visible bounds
for (Composite parent : parents) {
Rectangle parentClientArea = parent.getClientArea();
Point parentLocation = parent.toDisplay(parentClientArea.x, parentClientArea.y);
Rectangle parentBounds = new Rectangle(parentLocation.x, parentLocation.y, parentClientArea.width, parentClientArea.height);
intersection = intersection.intersection(parentBounds);
// if intersection has no size then it would be a waste of time to continue
if (intersection.width == 0 || intersection.height == 0) {
break;
}
}
overlay.setBounds(intersection);
}
}
I want to add vertical scroll bar to the screen that comes out of the below code. can you please suggest how it can be done?
public class SampleDialog extends TrayDialog {
public SampleDialog(final Shell shell) {
super(shell);
this.shell = shell;
}
#Override
public void create() {
super.create();
}
#Override
protected Control SampleDialog(final Composite parent) {
final GridLayout layout = new GridLayout();
layout.numColumns = 1;
parent.setLayout(layout);
createSampleText(parent);
createSampleCombo(parent);
}
}
where:
org.eclipse.jface.dialogs.TrayDialog;
org.eclipse.swt.layout.GridLayout;
org.eclipse.swt.widgets.Composite;
You can use a ScrolledComposite as the main parent for all your child controls in the dialog.
Some helpful snippets can be found here.
How to add Hyperlink in SWT Table column ?
I`m using org.eclipse.swt.widgets.Table class.
Is there any way to do this without using TableViewer, JFace ?
I tried this way but not working correctly (not showing hyperlinks).
for(int i=2; i<4; i++){
Hyperlink link = new Hyperlink(table, SWT.WRAP);
link.setText(temp[i]);
link.setUnderlined(true);
TableEditor editor = new TableEditor(table);
editor.setEditor(link, tableItem[index-1], i); //set hyperlinks in column i
}
Below is one way to draw the hyperlink using TableView with a LabelProvider, as mentioned in Tonny Madsen's answer.
The code below just paints the hyperlink.
TableViewerColumn column = ...
column.setLabelProvider( new MyHyperlinkLabelProvider( tableViewerFiles.getTable() ));
private final class MyHyperlinkLabelProvider extends StyledCellLabelProvider {
MyHyperlink m_control;
private MyHyperlinkLabelProvider( Composite parent ) {
m_control = new MyHyperlink( parent, SWT.WRAP );
}
#Override
protected void paint( Event event, Object element ) {
String sValue = ... [Get cell value from row element]
m_control.setText( sValue );
GC gc = event.gc;
Rectangle cellRect = new Rectangle( event.x, event.y, event.width, event.height );
cellRect.width = 4000;
m_control.paintText( gc, cellRect);
}
}
private class MyHyperlink extends Hyperlink {
public MyHyperlink(Composite parent, int style) {
super(parent, style);
this.setUnderlined(true);
}
#Override
public void paintText(GC gc, Rectangle bounds) {
super.paintText(gc, bounds);
}
}
Yes, that is certainly possible. To do this you have to implement SWT.ItemPaint (and possibly also SWT.ItemErase and SWT.ItemMeassure).
It is easier with TableView though if you use the correct LabelProvider...
You need to set the size of the editor:
editor.grabHorizontal = true;
//or
editor.minimumWidth = 50;