SWT Button Dropdown Control - java

Is there a standard SWT control that resembles a button which displays an arrow and opens a dropdown menu when pressed and is not a toolbar-only control?
It would be something like this:
It is similar to a combo box control, except that the "button" area would act more similarly to an actual button - its text would not change based on your selection, it would appear depressed when clicked, and the items would be used for actions or navigational purposes instead of for selection. It's also similar to a control available for toolbars, but I need to use it on a regular composite instead.
This is nearly doable simply by using regular button and popup-menu controls - however, I do not believe I can display the arrow next to the text on the button this way. Anyway, since this kind of control seems fairly common, I assumed there would be a standard way to use these two things as one.

I think, this is what you should do get Drop down menu behavior
Create Menu with style SWT.DROP_DOWN
Create MenuItems on Menu
if you want a button
Create a Button with style SWT.ARROW | SWT.DOWN
add SelectionListener
In SelectionListener, Create a Menu with style SWT.POP_UP and position the menu at the button location.
//code
public static void main(String[] args) {
Display display = new Display();
final Shell shell = new Shell(display);
shell.setSize(300, 200);
shell.setText("Button Example");
shell.setLayout(new RowLayout());
/**
*
* Approach1
*
*/
final Composite btnCntrl = new Composite(shell, SWT.BORDER);
btnCntrl.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
btnCntrl.setBackgroundMode(SWT.INHERIT_FORCE);
GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).spacing(0, 1).applyTo(btnCntrl);
CLabel lbl = new CLabel(btnCntrl, SWT.NONE);
lbl.setText("Animals");
Button btn = new Button(btnCntrl, SWT.FLAT|SWT.ARROW|SWT.DOWN);
btn.setLayoutData(new GridData(GridData.FILL_VERTICAL));
btn.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
super.widgetSelected(e);
Menu menu = new Menu(shell, SWT.POP_UP);
MenuItem item1 = new MenuItem(menu, SWT.PUSH);
item1.setText("Hare");
MenuItem item2 = new MenuItem(menu, SWT.PUSH);
item2.setText("Fox");
MenuItem item3 = new MenuItem(menu, SWT.PUSH);
item3.setText("Pony");
Point loc = btnCntrl.getLocation();
Rectangle rect = btnCntrl.getBounds();
Point mLoc = new Point(loc.x-1, loc.y+rect.height);
menu.setLocation(shell.getDisplay().map(btnCntrl.getParent(), null, mLoc));
menu.setVisible(true);
}
});
/***
*
*
* Approach 2
*
*/
final Composite btnCntrl2 = new Composite(shell, SWT.BORDER);
btnCntrl2.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
btnCntrl2.setBackgroundMode(SWT.INHERIT_FORCE);
GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).spacing(0, 1).applyTo(btnCntrl2);
CLabel lbl2 = new CLabel(btnCntrl2, SWT.NONE);
lbl2.setText("Animals");
Button btn2 = new Button(btnCntrl2, SWT.FLAT|SWT.ARROW|SWT.DOWN);
btn2.setLayoutData(new GridData(GridData.FILL_VERTICAL));
btn2.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
super.widgetSelected(e);
Shell menu = (Shell) btnCntrl2.getData("subshell");
if(menu != null && !menu.isDisposed()){
menu.dispose();
}
menu = new Shell(shell, SWT.NONE);
menu.setLayout(new FillLayout());
Table table = new Table(menu, SWT.FULL_SELECTION);
table.addListener(SWT.MeasureItem, new Listener() {
#Override
public void handleEvent(Event event) {
event.height = 20; //TODO: determine later
}
});
table.addListener(SWT.PaintItem, new Listener() {
#Override
public void handleEvent(Event event) {
Rectangle bounds = event.getBounds();
event.gc.setBackground(event.display.getSystemColor(SWT.COLOR_BLUE));
event.gc.drawLine(bounds.x, bounds.y+bounds.height-1, bounds.x+bounds.width, bounds.y+bounds.height-1);
}
});
TableItem tableItem= new TableItem(table, SWT.NONE);
tableItem.setText(0, "Hare");
TableItem tableItem2= new TableItem(table, SWT.NONE);
tableItem2.setText(0, "Pony" );
TableItem tableItem3= new TableItem(table, SWT.NONE);
tableItem3.setText(0, "Dog");
Point loc = btnCntrl2.getLocation();
Rectangle rect = btnCntrl2.getBounds();
Point mLoc = new Point(loc.x, loc.y+rect.height);
menu.setLocation(shell.getDisplay().map(btnCntrl2.getParent(), null, mLoc));
menu.pack();
menu.setVisible(true);
btnCntrl2.setData("subshell", menu);
}
});
display.addFilter(SWT.MouseDown, new Listener() {
#Override
public void handleEvent(Event event) {
Shell shell = (Shell) btnCntrl2.getData("subshell");
if(shell != null && !shell.getBounds().contains(event.display.map((Control)event.widget, null, new Point(event.x, event.y)))){
shell.dispose();
btnCntrl2.setData("subshell", null);
}
}
});
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}

This snippet shows how to use the described widget in a SWT toolbar. You can set the button text by using the item.setText() method.

This question is almost 10 years old, but just in case someone is still looking for a solution (like I just did ;) ):
I achieved a pretty close behaviour of your description using only a Button and a Menu using this approach: http://eclipseo.blogspot.com/2012/07/show-context-menu-programmatically.html
Button button = new Button(parent, SWT.PUSH);
button.setText("Animals");
Menu menu = new Menu(button);
MenuItem item = new MenuItem(menu, SWT.PUSH);
item.setText("hare");
menu.addListener(SWT.Show, new Listener() {
#Override
public void handleEvent(Event event) {
menu.setVisible(true);
}
});
button.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
menu.notifyListeners(SWT.Show, null);
}
});
The result is that the menu is shown when you (left) click on the button.
Bonus: to achieve the expand icon at the end, you can add a unicode character for a down triangle in the button text like so:
button.setText("Animals \u2BC6");
HTH,
Ben

Related

swt menu on click hide display composites

I am building a SWT application and have a menu created. Menu has multiple menu items like Add, Edit, Help. On click of each Menu Item, I want to show a composite which will display the details of it. I am able to build it, problem I am facing is, the space of hidden composite is not taken by visible composite. How can we make the composite occupy the entire space.
Also I am adding the selection listener to make the current composite visible and other composite hidden. In the current app there will multiple menu items and each one will have composite associated it. Listener needs reference of all composites to make them visible/hidden. Is there any better approach to do this.
public class MenuToggle {
boolean startup = true;
Menu menu, fileMenu, helpMenu;
Composite composite1,composite2;
public MenuToggle(Shell shell) {
createMenu(shell);
createFileView(shell);
createHelpView(shell);
startup = false;
}
public void createMenu(Shell shell) {
//Menu Bar
menu = new Menu(shell, SWT.BAR);
//File Menu
fileMenu = new Menu(shell, SWT.DROP_DOWN);
MenuItem fileMenuHeader = new MenuItem(menu, SWT.CASCADE);
fileMenuHeader.setText("&File");
fileMenuHeader.setMenu(fileMenu);
MenuItem fileSaveItem = new MenuItem(fileMenu, SWT.PUSH);
fileSaveItem.setText("&Save");
MenuItem fileExitItem = new MenuItem(fileMenu, SWT.PUSH);
fileExitItem.setText("E&xit");
//Help Menu
helpMenu = new Menu(shell, SWT.DROP_DOWN);
MenuItem helpMenuHeader = new MenuItem(menu, SWT.CASCADE);
helpMenuHeader.setText("&Help");
helpMenuHeader.setMenu(helpMenu);
MenuItem helpGetHelpItem = new MenuItem(helpMenu, SWT.PUSH);
helpGetHelpItem.setText("&Get Help");
shell.setMenuBar(menu);
fileSaveItem.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
composite1.setVisible(true);
((GridData)composite1.getLayoutData()).exclude = false;
composite2.setVisible(false);
((GridData)composite2.getLayoutData()).exclude = true;
composite2.layout(true, true);
}
});
helpGetHelpItem.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
composite1.setVisible(false);
((GridData)composite1.getLayoutData()).exclude = true;
composite2.setVisible(true);
((GridData)composite2.getLayoutData()).exclude = false;
composite2.layout(true, true);
}
});
}
public void createFileView(Shell shell) {
composite1 = new Composite(shell, SWT.BORDER);
composite1.setVisible(true);
GridData gd1 = new GridData(SWT.FILL, SWT.FILL, true, true);
composite1.setLayoutData(gd1);
composite1.setLayout(new GridLayout(1,true));
Label label = new Label(composite1, SWT.CENTER);
label.setBounds(composite1.getClientArea());
label.setText("Saved");
}
public void createHelpView(Shell shell) {
composite2 = new Composite(shell, SWT.BORDER);
composite2.setVisible(false);
GridData gd2 = new GridData(SWT.FILL, SWT.FILL, true, true);
composite2.setLayoutData(gd2);
composite2.setLayout(new GridLayout(1,true));
Label label1 = new Label(composite2, SWT.CENTER);
label1.setBounds(composite2.getClientArea());
label1.setText("No worries!");
}
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
shell.setText("Menu Display");
MenuToggle instance = new MenuToggle(shell);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
There are a number of issues here.
You are using FillLayout for the Shell layout, so the GridData you are setting on the composites is ignored. You must use GridLayout for the Shell:
public static void main(final String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new GridLayout()); // Changed
When you change the exclude settings you must call layout on the parent of the composite - the shell:
fileSaveItem.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(final SelectionEvent e) {
composite1.setVisible(true);
((GridData)composite1.getLayoutData()).exclude = false;
composite2.setVisible(false);
((GridData)composite2.getLayoutData()).exclude = true;
shell.layout(true, true); // change
}
});
helpGetHelpItem.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(final SelectionEvent e) {
composite1.setVisible(false);
((GridData)composite1.getLayoutData()).exclude = true;
composite2.setVisible(true);
((GridData)composite2.getLayoutData()).exclude = false;
shell.layout(true, true); // change
}
});
You are calling setBounds on the Label controls, this does not work when you are using layouts because the layout also calls setBounds and overrides your settings, use setLayoutData instead
Label label = new Label(composite1, SWT.CENTER);
label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); // replace
//label.setBounds(composite1.getClientArea()); // wrong
As for dealing with lots of Composite controls you could call shell.getChildren and loop through the child controls. Or add the composites to a List and loop through that.

Java SWT: Accelerator shortcut not working on PopUp context menus

I'm adding a PopUp menu to one of my widgets, a Table. It is not working! I only achieved to make work accelerator shortcuts if they are on top menu bar items, and not in popup context menu items. Why?
This is my code, but the table is inside a Composite which is inside another composite:
membersTable.setMenu(createMembersPopUpMenu(this));
private Menu createMembersPopUpMenu(Composite parent) {
Menu popUpMenu = new Menu(parent);
//Copy
copyMemberItem = new MenuItem(popUpMenu, SWT.PUSH);
copyMemberItem.setText("Copiar Miembro");
copyMemberItem.setAccelerator(SWT.MOD1 + 'C');
copyMemberItem.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
copyMember();
}
});
//Paste
pasteMemberItem = new MenuItem(popUpMenu, SWT.PUSH);
pasteMemberItem.setText("Pegar Miembro");
pasteMemberItem.setAccelerator(SWT.MOD1 + 'V');
pasteMemberItem.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
pasteMember();
}
});
return popUpMenu;
}

How to change the foreground color of specific items in a List?

When I press a button, I want to change the foreground color of the selected item in a List.
So far, I tried this:
list.setForeground(display.getSystemColor(SWT.COLOR_RED));
but it changes the foreground color of all the items, not just the selected one.
Any ideas how to solve this?
Doing this with a List would require custom drawing. You are better off using a Table instead (or even a TableViewer depending on your requirements). Here is an example of a table that does what you want:
public static void main(String[] args)
{
final Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new GridLayout(1, false));
shell.setText("StackOverflow");
final Table table = new Table(shell, SWT.BORDER | SWT.MULTI);
table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
for (int i = 0; i < 10; i++)
{
TableItem item = new TableItem(table, SWT.NONE);
item.setText("item " + i);
}
Button button = new Button(shell, SWT.PUSH);
button.setText("Color selected");
button.addListener(SWT.Selection, new Listener()
{
#Override
public void handleEvent(Event arg0)
{
List<TableItem> allItems = new ArrayList<>(Arrays.asList(table.getItems()));
TableItem[] selItems = table.getSelection();
for (TableItem item : selItems)
{
item.setForeground(display.getSystemColor(SWT.COLOR_RED));
allItems.remove(item);
}
for (TableItem item : allItems)
{
item.setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
}
}
});
shell.pack();
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
Before button press:
After button press:
Just a note: This is not the most efficient way to do it, but should give you the basic idea.
List does not supports what you want.
Use Table and Table items instead.
Each table item represent a row, and it has setForeground(Color) method.

Check checkbox selection from within Listener

Working away at the moment but have come up with a small problem in JFace.
I need to have a check box that allows the next button to become active.
Here is the code:
Button btnConfirm = new Button(container, SWT.CHECK);
btnConfirm.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
//missing if statement
setPageComplete(true);
}
});
btnConfirm.setBounds(330, 225, 75, 20);
btnConfirm.setText("Confirm");
What I'm trying to do is to build a menu where someone has to accept the terms and conditions before they can progress beyond a point. The default is to be unchecked but, when the box is checked, the next button will appear; if it is not, then the next button will remain inactive.
Just make the Button final and access it from within the Listener:
final Button btnConfirm = new Button(shell, SWT.CHECK);
btnConfirm.addSelectionListener(new SelectionAdapter()
{
#Override
public void widgetSelected(SelectionEvent e)
{
if (btnConfirm.getSelection())
setPageComplete(true);
else
setPageComplete(false);
}
});
Alternatively, get the Button from the SelectionEvent:
Button btnConfirm = new Button(shell, SWT.CHECK);
btnConfirm.addSelectionListener(new SelectionAdapter()
{
#Override
public void widgetSelected(SelectionEvent e)
{
Button button = (Button) e.widget;
if (button.getSelection())
setPageComplete(true);
else
setPageComplete(false);
}
});

SWT composite - redraw problem

I have a composite element, that initially has a Label. Now I call dispose on the it (the label) and create another label in the same container (composite elm), but I don't see the new text. It brings me to question how do I enable redraw on the composite, so that the new label (or any other component I might create) will render in place of the old one.
Here is the code I have (separated into a unit test for redraw a composite)
private Label createLabel( Composite parent) {
Label label = new Label(parent, SWT.NONE);
label.setAlignment(SWT.CENTER);
label.setLayoutData( new GridData( SWT.CENTER, SWT.CENTER, true, true) );
return label;
}
private void changeText() {
assert testCell != null : "Please initialize test cell";
testCell.getChildren()[0].dispose();
Label l = createLabel(testCell);
l.setText("New TexT");
testCell.redraw();
}
private void draw() {
Display display = new Display();
shell = new Shell(display);
shell.setLayout(new GridLayout(2,false));
testCell = new Composite(shell,SWT.BORDER);
testCell.setLayout(new GridLayout());
Label l = createLabel(testCell);
l.setText("Old Text");
Composite btnCell = new Composite(shell,SWT.NONE);
btnCell.setLayout(new GridLayout());
Button b = new Button(btnCell, SWT.PUSH);
b.setText("Change");
b.addListener(SWT.MouseDown, new Listener() {
public void handleEvent(Event e) {
changeText();
}
});
As you can see, I am calling redraw on the composite after I add a new element. Also, I have verified that after the call to dispose, testCell.getChildren().length returns 0, as expected, and when I create a new label, I get the same expression to return 1, verifying that the new element is indeed getting added to its parent composite container
Am I missing something here ?
In the changeText() function, the
testCell.redraw();
line should be replaced by
testCell.layout();
Or, if you want to resize it correctly you should use
shell.layout();.
I would say add a selectionListener on the label.
.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(final SelectionEvent e) {
//Change text by Label.setText();
}
}

Categories

Resources