I wanted to have a Text widget that could display a message in it when the user has not entered a value into the field yet. I extended composite and essentially wrapped a text field in it. Added a focus listener to remove the message on focus, and to replace the message when the focus is lost if the field is empty. That all works as expected.
The issue I am having is I wanted the prompt to be styled differently when it is placed in the text field. The font does not seem to be being used initially. Once the field has had focus and loses focus it looks correct.
For example this is how it looks when it initially loads:
And this is how it should look on initial load and how it looks after having and lost focus:
It gets a little stranger, as when I run this inside a simple shell, it works how it should. When I run it as an Eclipse application is when it does not get styled correctly.
Here is the code for my composite:
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Text;
/**
* The <code>PromptingTextInput</code> component is a small enhancement to
* standard <code>Text</code>. It adds the ability to specify a prompt value
* that displays when the text is empty and the field does not have focus.
*/
public class PromptingTextInput extends Composite {
private String prompt;
private Text input;
private boolean textEmpty;
Font emptyFont;
Font inputFont;
public PromptingTextInput(String prompt, Composite parent, int style, boolean passwordField) {
super(parent, style);
this.prompt = prompt;
setLayout(new FillLayout());
this.textEmpty = true;
this.input = new Text(this, (passwordField ? SWT.PASSWORD : SWT.NONE));
setEmptyInputStyle();
this.input.setText(this.prompt);
this.input.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent e) {
PromptingTextInput.this.focusGained();
}
public void focusLost(FocusEvent e) {
PromptingTextInput.this.focusLost();
}
});
addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
disposeFonts();
}
});
}
protected void focusGained() {
if (this.textEmpty) {
this.input.setText("");
setInputStyle();
}
}
protected void focusLost() {
if (input.getText() == null || input.getText().trim().length() == 0) {
this.input.setText(this.prompt);
setEmptyInputStyle();
this.textEmpty = true;
} else {
this.textEmpty = false;
}
}
protected void setInputStyle() {
if (this.inputFont == null){
this.inputFont = new Font(Display.getCurrent(), "Verdana", 8, SWT.DEFAULT);
}
this.input.setFont(this.inputFont);
this.input.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_BLACK));
}
protected void setEmptyInputStyle() {
if (this.emptyFont == null){
this.emptyFont = new Font(Display.getCurrent(), "Verdana", 6, SWT.ITALIC);
}
this.input.setFont(this.emptyFont);
this.input.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_GRAY));
}
public String getPrompt() {
return prompt;
}
public void setPrompt(String prompt) {
this.prompt = prompt;
if(!this.input.isFocusControl()){
this.input.setText(this.prompt);
setEmptyInputStyle();
}
}
public Text getInput() {
return input;
}
public boolean isTextEmpty() {
return textEmpty;
}
public String getText() {
return this.input.getText();
}
public void addModifyListener (ModifyListener listener) {
this.input.addModifyListener(listener);
}
public void disposeFonts(){
if (this.inputFont != null){
this.inputFont.dispose();
}
if (this.emptyFont != null){
this.emptyFont.dispose();
}
}
}
UPDATE: As Baz has shown this is not an issue in Indigo and only seems to be an issue with an E4 app in Juno.
Building upon sambi's answer, I got the following working. Maybe this works in Juno:
private static Font italic;
public static void main(String[] args) {
final Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new GridLayout(1,false));
italic = new Font(Display.getCurrent(), "Verdana", 6, SWT.ITALIC);
final Text text = new Text(shell, SWT.BORDER);
text.addListener(SWT.Paint, new Listener() {
#Override
public void handleEvent(Event event) {
if(text.getText().length() < 1 && !text.isFocusControl())
{
GC gc = event.gc;
gc.setFont(italic);
gc.setForeground(display.getSystemColor(SWT.COLOR_GRAY));
Point size = text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
/* Strangely the y positioning doesn't work correctly */
//gc.drawText("Please enter text", 1, (size.y / 2) - (italic.getFontData()[0].getHeight() / 2));
gc.drawText("Please enter text", 1, 4);
}
}
});
text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
Button button = new Button(shell, SWT.PUSH);
button.setText("Dummy");
button.forceFocus();
shell.setSize(200, 100);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
italic.dispose();
display.dispose();
}
Without focus and empty text:
With focus or text:
You could use Text.setMessage (String message). The problem with this is you will not be able to customize much with font size and fore ground...etc.
To customize it, you can actually draw message with in the bounds of Text.
Related
i have another question. I use a ModifyListener for one textfield to activate and deactivate the OK-Button in a swt dialog. It works great.
Now I want to add a ModifyListener for another textfield. I want that the OK-Button only is activated if in both text fields is min one char.
This is the code of the two fields:
descriptionText.addModifyListener(new ModifyListener(){
public void modifyText(ModifyEvent e) {
Text text = (Text) e.widget;
if (text.getText().length() == 0) {
getButton(IDialogConstants.OK_ID).setEnabled(false);
}
if (text.getText().length() >= 1) {
getButton(IDialogConstants.OK_ID).setEnabled(true);
}
}
});
}
the second field:
ccidText.addModifyListener(new ModifyListener(){
public void modifyText(ModifyEvent e) {
Text text = (Text) e.widget;
if (text.getText().length() == 0) {
getButton(IDialogConstants.OK_ID).setEnabled(false);
}
if (text.getText().length() >= 1){
getButton(IDialogConstants.OK_ID).setEnabled(true);
}
}
});
}
I know that it doesn´t work because there are no dependencies between the two buttons.
How can i combine it?
I want to set the ok-button false while both modifylistener detect a char.
If i delete all chars in one testfield the button must be deactivated again.
Thank u.
You can use the same Listener for both Text fields and add it for SWT.KeyUp:
public static void main(String[] args)
{
final Display display = new Display();
Shell shell = new Shell(display);
shell.setText("StackOverflow");
shell.setLayout(new FillLayout(SWT.VERTICAL));
final Text first = new Text(shell, SWT.BORDER);
final Text second = new Text(shell, SWT.BORDER);
final Button button = new Button(shell, SWT.PUSH);
button.setText("disabled");
button.setEnabled(false);
Listener listener = new Listener()
{
#Override
public void handleEvent(Event e)
{
String firstString = first.getText();
String secondString = second.getText();
button.setEnabled(!isEmpty(firstString) && !isEmpty(secondString));
button.setText(button.isEnabled() ? "enabled" : "disabled");
}
};
first.addListener(SWT.KeyUp, listener);
second.addListener(SWT.KeyUp, listener);
shell.pack();
shell.setSize(300, shell.getSize().y);
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
private static boolean isEmpty(String input)
{
if(input == null)
return true;
else
return input.trim().isEmpty();
}
Looks like this:
The code will basically (on each key stroke) check if both Texts are empty. If so, disable the Button, else enable it.
Here is my full coding.I have Two class firstone MyDateTime and Second one is Employee.
i have included currently working coding of mine.For the EmployeePart class,AbstractEditorPart is our own parent class Which is extended
public class MyDateTime extends DateTime{
public DateTime(Composite parent, int style)
{
super(parent, style);
}
public Date getValue()
{
Date date = new Date(getYear(), getMonth(), getDay());
return date;
}
}
public Class EmployeePart extends AbstractEditorPart(
private MyDateTime currentDate;
public void createBody(Composite parent){
currentDate=Util.createDateChooserCombo(parent, toolkit, "Date:", 2);
}
public void save(Employee input){
return null;
}
}
}
Turns out to be a little more complicated than I first thought.
One solution is to define a TabList for the Composite that contains your Widgets.
This way you can first define in which order you want them to be traversed.
Then add a Listener to each of the Widgets you want to traverse. This Listener will determine the next item in the TabList and force the focus on this item when either Tab or Enter is pressed.
Here is some example code:
public static void main(String[] args)
{
Display display = Display.getDefault();
Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
final Composite content = new Composite(shell, SWT.NONE);
content.setLayout(new FillLayout());
Text first = new Text(content, SWT.BORDER);
Text second = new Text(content, SWT.BORDER);
content.setTabList(new Control[] {first, second});
Listener enterListener = new Listener()
{
#Override
public void handleEvent(Event event)
{
/* Is it a traverse via Tab or Enter? */
if(event.keyCode == SWT.CR || event.keyCode == SWT.TRAVERSE_RETURN || event.keyCode == SWT.TRAVERSE_TAB_NEXT)
{
/* Get source of event */
Widget source = event.widget;
/* Get traverse order of content composite */
Control[] tabList = content.getTabList();
/* Try to find current position in the tab list */
for(int i = 0; i < tabList.length; i++)
{
if(source.equals(tabList[i]))
{
/* Get the next item in the tab list */
Control nextControl = tabList[(i + 1) % tabList.length];
/* And force the focus on this item */
nextControl.setFocus();
nextControl.forceFocus();
return;
}
}
}
}
};
first.addListener(SWT.KeyUp, enterListener);
second.addListener(SWT.KeyUp, enterListener);
shell.pack();
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
I have an SWT Composite that I need to pass to some other code which will add children to it at will. Is there any way to be notified that a child of the composite received and lost focus?
Just to make sure it's clear, I cannot add listeners to each child, because I'm not in charge of creating those controls. A child could be added at any time.
As noted by Favonius, you can hook layout events like SWT.Resize to determine when you're being painted and recompute your child hierarchy, adding listeners as appropriate. Another option is simply to listen for all focus events and only pay attention to those that are for controls that you're interested in.
Displays have filters which, like listeners, are notified of events, however filters differ in that they are run before listeners, they have the opportunity to cancel events, and they are notified for all of a type of event on the entire Display.
You could thus use a Filter to examine all focus events and determine if it's one that you're interested in. For example:
public class MyControl extends Composite
{
private final Listener focusListener;
public MyControl(final Composite parent, final int style)
{
/* initialize the control... */
focusListener = new Listener()
{
public void handleEvent(Event event)
{
if (!(event.widget instanceof Control))
{
return;
}
boolean isOurChild = false;
for (Control c = (Control) event.widget; c != null; c = c.getParent())
{
if (c == container)
{
isOurChild = true;
break;
}
}
if (isOurChild)
{
System.out.println("Our child is " + (event.type == SWT.FocusIn ? "focused" : "unfocused"));
}
}
};
getDisplay().addFilter(SWT.FocusIn, focusListener);
getDisplay().addFilter(SWT.FocusOut, focusListener);
addDisposeListener(new DisposeListener()
{
public void widgetDisposed(DisposeEvent e)
{
getDisplay().removeFilter(SWT.FocusIn, focusListener);
getDisplay().removeFilter(SWT.FocusOut, focusListener);
}
});
}
}
Do note the javadoc for Display's warnings about using filters:
They should generally be avoided for performance, debugging and code maintenance reasons.
Obviously you're looking at performance trade-offs in either solution - depending on what type of application you're delivering and your users' workflow, it may make more sense to add focus listeners when you resize, or it may make more sense to simply listen to all focus events and ignore the ones you're not interested in.
Have you checked this link: SWT: notify a composite that it has a new child
As per the proposed solution in the above link the only possible solution is to use the resize event. Based on that see the following code, which adds a focus listener on all the immediate child nodes. Though the solution itself is not very elegant.
Test Code
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class SWTApplication
{
public static void main(String[] args) {
new SWTApplication().initSystem("Children Notification");
}
private Display display;
private Shell shell;
public void initSystem(String windowName)
{
display = new Display();
shell = new Shell(display);
shell.setText(windowName);
shell.setLayout(new GridLayout(6, true));
shell.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
final TestFocusListener listener = new TestFocusListener();
shell.addControlListener(new ControlListener() {
public void controlResized(ControlEvent e) {
if(e.getSource() instanceof Shell)
{
Shell s = (Shell)e.getSource();
Control[] children = s.getChildren();
for (int i = 0; i < children.length; i++)
{
Control c = children[i];
c.removeFocusListener(listener);
c.addFocusListener(listener);
}
}
}
public void controlMoved(ControlEvent e) {
}
});
createControls();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
private void createControls()
{
String[] name = {"a", "b", "c", "d", "e", "f"};
for(int i=0; i<6; i++)
{
Button button = new Button(shell, SWT.PUSH);
button.setText(name[i] + " button");
button.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
shell.setSize(shell.computeSize(SWT.DEFAULT, SWT.DEFAULT));
}
}
class TestFocusListener implements FocusListener
{
public void focusGained(FocusEvent e) {
Object src = e.getSource();
if(src instanceof Button)
{
System.out.println("Focus gained: " + ((Button)src).getText());
}
}
public void focusLost(FocusEvent e) {
Object src = e.getSource();
if(src instanceof Button)
{
System.out.println("Focus lost: " + ((Button)src).getText());
}
}
}
}
I've been banging away at this for a while now and I can't seem to get anywhere. I've tried all of the examples I can find online and nothing seems to work! I haven't been able to find much on this problem which leads me to think I'm missing something basic. . .
In my Eclipse RCP program I want to display a dialog that will show a list of errors that occurred while loading a data file. I have overridden TitleAreaDialog and simply want to display a scrollable Text containing the list of errors and an OK button.
The problem is that the Text vertical scroll bars don't become active - the Text just grows taller to fit the text. This makes the dialog window height increases until it either fits the Text box or until it reaches the height of the screen - and then it just cuts off the bottom of the Text box.
How do I prevent the Dialog/Text box from growing too large? What am I missing?
Thanks for your help!!
-Christine
...
Here is a simple program showing my Dialog:
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.swt.*;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
public class ScrollableDialogRunner {
public static void main(String[] args) {
System.out.println("starting");
Display display = new Display ();
Shell shell = new Shell (display);
String errors = "one\ntwo\nthree\nfour\nfive\n";
for(int i = 0; i < 5; i++){
errors += errors;
}
ScrollableDialog dialog = new ScrollableDialog(shell, "Errors occurred during load", "The following errors occurred while loaded file 'x.data'", errors);
dialog.open();
}
}
class ScrollableDialog extends TitleAreaDialog {
private String title;
private String text;
private String scrollableText;
public ScrollableDialog(Shell parentShell, String title, String text, String scrollableText) {
super(parentShell);
this.title = title;
this.text = text;
this.scrollableText = scrollableText;
}
#Override
protected Control createDialogArea(Composite parent) {
GridLayout layout = new GridLayout();
layout.numColumns = 1;
parent.setLayout(layout);
GridData gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.horizontalAlignment = GridData.FILL;
Text scrollable = new Text(parent, SWT.BORDER | SWT.V_SCROLL);
scrollable.setLayoutData(gridData);
scrollable.setText(scrollableText);
return parent;
}
#Override
public void create() {
super.create();
setTitle(title);
setMessage(text, IMessageProvider.ERROR);
}
#Override
protected void createButtonsForButtonBar(Composite parent) {
Button okButton = createButton(parent, OK, "OK", true);
okButton.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
close();
}
});
}
#Override
protected boolean isResizable() {
return false;
}
}
Assign a size to the dialog; otherwise, the dialog will layout the children asking them for their "preferred" size (which is infinite for the text widget) and will resize itself accordingly.
[EDIT] This version works. See my comments for details.
class ScrollableDialog extends TitleAreaDialog {
private String title;
private String text;
private String scrollableText;
public ScrollableDialog(Shell parentShell, String title, String text, String scrollableText) {
super(parentShell);
this.title = title;
this.text = text;
this.scrollableText = scrollableText;
}
#Override
protected Control createDialogArea(Composite parent) {
Composite composite = (Composite) super.createDialogArea (parent); // Let the dialog create the parent composite
GridData gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.horizontalAlignment = GridData.FILL;
gridData.grabExcessVerticalSpace = true; // Layout vertically, too!
gridData.verticalAlignment = GridData.FILL;
Text scrollable = new Text(composite, SWT.BORDER | SWT.V_SCROLL);
scrollable.setLayoutData(gridData);
scrollable.setText(scrollableText);
return composite;
}
#Override
public void create() {
super.create();
// This is not necessary; the dialog will become bigger as the text grows but at the same time,
// the user will be able to see all (or at least more) of the error message at once
//getShell ().setSize (300, 300);
setTitle(title);
setMessage(text, IMessageProvider.ERROR);
}
#Override
protected void createButtonsForButtonBar(Composite parent) {
Button okButton = createButton(parent, OK, "OK", true);
okButton.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
close();
}
});
}
#Override
protected boolean isResizable() {
return true; // Allow the user to change the dialog size!
}
}
I have an SWT Tree in my application that contains an infinite data structure. Upon expanding an item, I generate its children. On Windows though, users can press "*", triggering an "expand all descendants" action, and my application hangs.
There are two acceptable behaviors for me when the user presses "*":
Expand all children of the selected element, but only to the next level
Do nothing
In either case, I will still need to be able to expand items as deep as required (by clicking on the [+] icon, or by pressing "+"), so limiting the tree depth is not a solution. Is there another way that I can achieve either of the above without modifying SWT classes?
I got this far -- maybe it helps someone:
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
public class SWTInfiniteTree {
private boolean expanding;
public SWTInfiniteTree() {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new GridLayout());
Tree tree = new Tree(shell, SWT.BORDER);
tree.setLayoutData(new GridData(GridData.FILL_BOTH));
createItem(tree, "ITEM1");
createItem(tree, "ITEM2");
createItem(tree, "ITEM3");
tree.addTreeListener(new TreeListener() {
#Override
public void treeExpanded(TreeEvent e) {
TreeItem parent = (TreeItem) e.item;
if (expanding) {
e.doit = false;
} else {
expanding = true;
parent.removeAll();
createItem(parent, ".1");
createItem(parent, ".2");
createItem(parent, ".3");
}
}
#Override
public void treeCollapsed(TreeEvent e) {
}
});
tree.addKeyListener(new KeyListener() {
#Override
public void keyReleased(KeyEvent e) {
expanding = false;
}
#Override
public void keyPressed(KeyEvent e) {
}
});
tree.addMouseListener(new MouseListener() {
#Override
public void mouseUp(MouseEvent e) {
expanding = false;
}
#Override
public void mouseDown(MouseEvent e) {
}
#Override
public void mouseDoubleClick(MouseEvent e) {
expanding = false;
}
});
shell.setSize(300, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private TreeItem createItem(Widget parent, String text) {
TreeItem item;
if (parent instanceof Tree) {
item = new TreeItem((Tree) parent, SWT.NULL);
item.setText(text);
} else {
item = new TreeItem((TreeItem) parent, SWT.NULL);
item.setText(((TreeItem) parent).getText() + text);
}
// So that we have a [+] icon
item.setItemCount(1);
return item;
}
public static void main(String[] args) {
new SWTInfiniteTree();
}
}
What it does is it expands the first element, and then goes into "won't expand more" mode, which is lifted whenever a key or the mouse button is released. However, for some reason it will expand my freshly generated items.
I hope someone has a better solution.