Weex customized native android component Rich Text height issue - richtext

I am creating a Richtext(WXTextView.java) view component in Weex by extending WXComponent. As Richtext component is not available in weex android sdk and "v-html" tag is also not supported in weex text component.
When my Richtext element is wrapped inside a div, the element is not visible. I have to manually add height to its parent div to make it visible.
<div class="parent">
<textView
ref="nativeTextView"
:style="{
color: '#ff6600',
fontSize: '40px',
maxLine: 2,
borderWidth: 2,
borderStyle: 'solid',
borderColor: 'green',
}"
text="ABCDEF"
/>
</div>
Giving height to the parent doesn't solve my purpose because text length is dynamic. I want to make this behavior just like default weex text component supporting rich text.
WXTextView.java
public class WXTextView extends WXComponent<TextView> {
private WXVContainer mContainer;
private int mHeight;
public WXTextView(WXSDKInstance instance, WXDomObject dom, WXVContainer parent) {
super(instance, dom, parent);
mContainer = parent;
}
#Override
protected TextView initComponentHostView(#NonNull Context context) {
TextView textView = new TextView(context);
setProperty(WXComponent.PROP_FIXED_SIZE, WXComponent.PROP_FS_WRAP_CONTENT);
textView.setIncludeFontPadding(false);
textView.setTextSize(WXText.sDEFAULT_SIZE);
return textView;
}
#WXComponentProp(name = "text")
public void setText(String text) {
getHostView().setText(Html.fromHtml(text));
updateUI();
}
private void updateUI() {
ViewGroup.LayoutParams params = mContainer.getRealView().getLayoutParams();
params.height = getHeight();
mContainer.getRealView().setLayoutParams(params);
mContainer.getRealView().invalidate();
}
#WXComponentProp(name = "ellipsize")
public void setEllipsize(String positionString) {
try {
int position = Integer.parseInt(positionString);
TextUtils.TruncateAt truncateType;
switch (position) {
case 0:
truncateType = TextUtils.TruncateAt.START;
break;
case 1:
truncateType = TextUtils.TruncateAt.MIDDLE;
break;
default:
truncateType = TextUtils.TruncateAt.END;
break;
}
getHostView().setEllipsize(truncateType);
updateUI();
} catch (Exception exception) {
exception.printStackTrace();
}
}
#WXComponentProp(name = "maxLine")
public void setMaxLine(String lineString) {
try {
int lineCount = Integer.parseInt(lineString);
getHostView().setMaxLines(lineCount);
updateUI();
} catch (Exception exception) {
exception.printStackTrace();
}
}
#JSMethod
public void getElementSpecs(JSCallback callback){
Log.d("nikhil", "android getHeight: " + getHostView().getHeight());
Map<String, Object> data = new HashMap<>();
data.put("width", getHostView().getMeasuredWidth());
data.put("height", getHostView().getMeasuredHeight());
data.put("positionX", getHostView().getX());
data.put("positionY", getHostView().getY());
callback.invoke(data);
}
#WXComponentProp(name = "color")
public void setColor(String color) {
getHostView().setTextColor(Color.parseColor(color));
}
#WXComponentProp(name = "fontSize")
public void setFontSize(String sizeString) {
int lastIndex = sizeString.indexOf("px");
if (lastIndex == -1) {
lastIndex = sizeString.length();
}
sizeString = sizeString.substring(0, lastIndex);
int size = Integer.parseInt(sizeString);
getHostView().setTextSize(size);
updateUI();
}
public int getHeight() {
getHostView().setText(getHostView().getText());
getHostView().setTextSize(TypedValue.COMPLEX_UNIT_PX, getHostView().getTextSize());
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(mContainer.getRealView().getLayoutParams().width,
View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
getHostView().measure(widthMeasureSpec, heightMeasureSpec);
mHeight = getHostView().getMeasuredHeight();
return mHeight;
}
}

Text is the most complicated component in Weex. In order to achieve similar behavior like weex text, you need extend WXDomObject as text extend WXTextDomObject, and implement your own text measure function.
In fact, I have written a richtext component in weex which will be released soon.

Related

Customize the Xamarin.Forms Picker Popup List

I know how to create a custom renderer to customize the actual text of the Xamarin forms picker, but how do you customize, say, the background color or text of the list that pops up when you click on the picker text box?
You can refer to the following code :
in iOS
using System;
using Xamarin.Forms;
using xxx;
using xxx.iOS;
using UIKit;
using Xamarin.Forms.Platform.iOS;
using Foundation;
[assembly:ExportRenderer(typeof(MyPicker), typeof(MyiOSPicker))]
namespace xxx.iOS
{
public class MyiOSPicker:PickerRenderer,IUIPickerViewDelegate
{
IElementController ElementController => Element as IElementController;
public MyiOSPicker()
{
}
[Export("pickerView:viewForRow:forComponent:reusingView:")]
public UIView GetView(UIPickerView pickerView, nint row, nint component, UIView view)
{
UILabel label = new UILabel
{
//here you can set the style of item!!!
TextColor = UIColor.Blue,
Text = Element.Items[(int)row].ToString(),
TextAlignment = UITextAlignment.Center,
};
return label;
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if(Control!=null)
{
UIPickerView pickerView = (UIPickerView)Control.InputView;
pickerView.WeakDelegate = this;
pickerView.BackgroundColor = UIColor.Yellow; //set the background color of pickerview
}
}
}
}
in Android
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using xxx;
using xxx.Droid;
using Android.Widget;
using Android.App;
using System.Linq;
[assembly: ExportRenderer(typeof(MyPicker), typeof(MyAndroidPicker))]
namespace xxx.Droid
{
public class MyAndroidPicker:PickerRenderer
{
IElementController ElementController => Element as IElementController;
public MyAndroidPicker()
{
}
private AlertDialog _dialog;
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || e.OldElement != null)
return;
Control.Click += Control_Click;
}
protected override void Dispose(bool disposing)
{
Control.Click -= Control_Click;
base.Dispose(disposing);
}
private void Control_Click(object sender, EventArgs e)
{
Picker model = Element;
var picker = new NumberPicker(Context);
if (model.Items != null && model.Items.Any())
{
// set style here
picker.MaxValue = model.Items.Count - 1;
picker.MinValue = 0;
picker.SetBackgroundColor(Android.Graphics.Color.Yellow);
picker.SetDisplayedValues(model.Items.ToArray());
picker.WrapSelectorWheel = false;
picker.Value = model.SelectedIndex;
}
var layout = new LinearLayout(Context) { Orientation = Orientation.Vertical };
layout.AddView(picker);
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, true);
var builder = new AlertDialog.Builder(Context);
builder.SetView(layout);
builder.SetTitle(model.Title ?? "");
builder.SetNegativeButton("Cancel ", (s, a) =>
{
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
// It is possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
Control?.ClearFocus();
_dialog = null;
});
builder.SetPositiveButton("Ok ", (s, a) =>
{
ElementController.SetValueFromRenderer(Picker.SelectedIndexProperty, picker.Value);
// It is possible for the Content of the Page to be changed on SelectedIndexChanged.
// In this case, the Element & Control will no longer exist.
if (Element != null)
{
if (model.Items.Count > 0 && Element.SelectedIndex >= 0)
Control.Text = model.Items[Element.SelectedIndex];
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
// It is also possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
Control?.ClearFocus();
}
_dialog = null;
});
_dialog = builder.Create();
_dialog.DismissEvent += (ssender, args) =>
{
ElementController?.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
};
_dialog.Show();
}
}
}

I'm stuck at slick graphics

I'm trying to make a game, using slick2d, and lwjgl. I don't get why this code doesn't work
firstStage.java
package net.CharlesDickenson;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
public class firstStage extends BasicGameState {
public bossVar bossChecker() {
if(isBeforeMiddleBoss) return bossVar.beforeBoss;
if(isMiddleBoss) return bossVar.Middle;
if(isBeforeBoss) return bossVar.beforeBoss;
if(isBoss) return bossVar.Boss;
return null;
}
#SuppressWarnings("static-access")
public firstStage(int state) {
this.state = state;
}
#Override
public void init(GameContainer _arg0, StateBasedGame _arg1)
throws SlickException {
scoreBoard = new Image("res/scoreBoard.png");
backs = new Image("res/1stageBack.gif");
isBeforeMiddleBoss = true;
isMiddleBoss = false;
isBeforeBoss = false;
isBoss = false;
_arg0.setShowFPS(false);
}
#Override
public void render(GameContainer arg0, StateBasedGame _arg1, Graphics _g)
throws SlickException {
this._g = _g;
new Mob().getGraphics(_g);//i passed graphics
new Char().getGraphics(_g);//i passed graphics
new Bullet().getGraphics(_g);//i passed graphics
_g.drawImage(scoreBoard, 550, 5);
_g.drawImage(backs, 10, 10);
_g.drawString(fps, 580, 570);
_g.drawString("High Score-> Not avaiable", 560, 60);
_g.drawString("Score-> " + currScore, 595, 80);
}
#Override
public void update(GameContainer _arg0, StateBasedGame _arg1, int arg2)
throws SlickException {
fps = "Frame Per Second-> " + _arg0.getFPS();
bossVar b = bossChecker();
switch(b) {
case beforeMiddle :
break;
case Boss :
break;
default:
break;
}
}
#SuppressWarnings("static-access")
#Override
public int getID() {
return this.state;
}
private static int state;
private static int currScore = 0;
private static final int originX = 270;
private static final int originY = 490;
public static int X = originX;
public static int Y = originY;
private static String fps;
private Image scoreBoard;
private Image backs;
private Graphics _g;
public boolean isBeforeMiddleBoss;
public boolean isMiddleBoss;
public boolean isBeforeBoss;
public boolean isBoss;
}
Char.java
package net.CharlesDickenson;
import org.lwjgl.input.Keyboard;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
public class Char extends Bullet implements Entity {
#Override
public void getGraphics(Graphics _g) {
this._g = _g;//so i got graphics, but
if(!isInit) return;
_g.drawImage(Char, getCharX(), getCharY());//this codes doesn't works.
}
#Override
public int getCharX() {
switch(VarTracker.stage) {
case 1:
return firstStage.X;
}
return 0;
}
#Override
public int getCharY() {
switch(VarTracker.stage) {
case 1:
return firstStage.Y;
}
return 0;
}
public void setCharX(int i) {
System.out.println("asdgagsd");
switch(VarTracker.stage) {
case 1:
firstStage.X += i;
}
}
public void setCharY(int i) {
System.out.println("asdgagsd");
switch(VarTracker.stage) {
case 1:
firstStage.Y += i;
}
}
#Override
public void update() {
if(!isInit) return;
_g.drawImage(Char, getCharX(), getCharY());//this code doesn't work, too.
up = Keyboard.isKeyDown(Keyboard.KEY_UP);
down = Keyboard.isKeyDown(Keyboard.KEY_DOWN);
left = Keyboard.isKeyDown(Keyboard.KEY_LEFT);
right = Keyboard.isKeyDown(Keyboard.KEY_RIGHT);
shift = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT);
z = Keyboard.isKeyDown(Keyboard.KEY_Z);
if(up && !shift) {
setCharY(6);
}
if(down && !shift) {
setCharY(-6);
}
if(left && !shift) {
setCharX(-6);
}
if(right && !shift) {
setCharX(6);
}
if(up && shift) {
setCharY(2);
}
if(down && shift) {
setCharY(-2);
}
if(left && shift) {
setCharX(-2);
}
if(right && shift) {
setCharX(2);
}
if(z) {
new Bullet().isFiring = true;
}
if(!z) {
new Bullet().isFiring = false;
}
}
#Override
public void init() {
System.out.println("<Char> Initializing...");
isInit = false;
try {
Char = new Image("res/char.png");
} catch (SlickException e) {
e.printStackTrace();
}
isInit = true;
System.out.println("<Char> Done with init()");
}
private boolean up;
private boolean down;
private boolean left;
private boolean right;
private boolean shift;
private boolean z;
private boolean isInit;
private Image Char;
private Graphics _g;
}
I passed graphics to other class using getGraphics method, to put a image, but it doesn't work.
at render method, it worked, but I can't put a image in other class.
The reason that it doesn't work is that you are using Graphics incorrectly. When Slick2d draws something, it uses the render method. This method is passed an instance of Graphics, to which you can draw stuff. When the call ends the Graphics object is no longer useful for anything. There is thus no reason to pass it to anything that doesn't use it immediately.
What you want to do is create a render method in your Mob, Char and Bullet classes. Make instances of said classes outside of the render method, for instance in init and store them in some data structure, for instance a List. In the render method, you simple traverse the list and call render or draw on each element. A quick example:
// Give the Entity interface two methods if they don't exist already:
public interface Entity {
void render(Graphics g);
void update(int delta);
}
// In firststage.java
List<Entity> list;
// In the init() method
public void init(GameContainer container, StateBasedGame game)
throws SlickException {
...
list = new ArrayList<Entity>();
list.add(new Mob());
list.add(new Char());
list.add(new Bullet());
}
// In the render method
public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException {
...
for (Entity e : list) {
e.draw(g);
}
}
// In the update method
public void update(GameContainer container, StateBasedGame game, int delta)
throws SlickException {
...
for (Entity e : list) {
e.update(delta);
}
}
TL;DR version: The Graphics object exists only to be drawn to in a single render call.
Render is called many times a second, so object creation in that method is not recommended.
Object oriented programming is good at modeling objects. Games tend to model a lot of objects. Make use of it.

How to set the color of the first column of a Listview item?

I have a Listview (shown in an AlertDialog) that is composed by two columns, made with a HashMap (got the idea somewhere here).
Each column is a TextView. It works well.
Now I want to change the textcolor of the first column of a given row, but I have no idea on how to pick and set this... I googled for hours! Any clues??
This is my code (lists is a SortedSet):
public void showlistdesc () {
ListView listview = new ListView(this);
listview.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
listview.setSoundEffectsEnabled(true);
listview.setSelector(R.drawable.selector);
Integer i=0, pos = 0;
ArrayList<HashMap<String, String>> mylist = new ArrayList<HashMap<String, String>>();
HashMap<String, String> map;
for (String l : lists) {
if (l.equals(currlist)) pos = i;
i++;
map = new HashMap<String, String>();
map.put("list", l);
map.put("desc", getlistdesc(l, false));
mylist.add(map);
}
listview.setAdapter(new SimpleAdapter(this, mylist, R.layout.lists_row,
new String[] {"list", "desc"}, new int[] {R.id.list1, R.id.desc1}));
AlertDialog.Builder builder = new AlertDialog.Builder(AveActivity.this)
.setView(listview)
.setIcon(R.drawable.ic_launcher)
.setTitle(lists.size() + " bird lists in databases");
final AlertDialog dial = builder.create();
//Scrolls the listview to this position at top
listview.setSelection(pos);
dial.show();
}
Thanks!
EDIT
Tried to extend SimpleAdapter with the following code, and all I can see are solid black rows:
public class MySimpleAdapter extends SimpleAdapter {
private Context context;
public MySimpleAdapter(Context context,List<? extends Map<String, ?>> data,
int resource, String[] from, int[] to) {
super(context, data, resource, from, to);
this.context = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater inflater = (LayoutInflater) this.context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.lists_row, null);
}
TextView list1 = ((TextView) v.findViewById(R.id.list1));
TextView desc1 = ((TextView) v.findViewById(R.id.desc1));
if (v.isPressed()) {
v.setBackgroundColor(context.getResources().getColor(R.color.pressed));
list1.setTypeface(null, 1);
list1.setTextColor(context.getResources().getColor(R.color.selected));
} else {
v.setBackgroundColor(context.getResources().getColor(R.color.black));
if (v.isSelected()) {
list1.setTypeface(null, 1);
list1.setTextColor(context.getResources().getColor(R.color.checked));
desc1.setTextColor(context.getResources().getColor(R.color.white));
} else {
list1.setTypeface(null, 0);
list1.setTextColor(context.getResources().getColor(R.color.normal));
desc1.setTextColor(context.getResources().getColor(R.color.lesswhite));
}
}
return v;
}
}
Now you have special requirement, it is no longer simple, you can't use SimpleAdapter to achieve the effect you are asking. You will have to extend BaseAdapter and in the getView() method you can do the following:
if (position == 0) {
convertView.setBackground(color1);
} else {
convertView.setBackground(color2);
}
Or if what you want is a Header, you can use ListView.addHeaderView() without much hassle.
I found a solution, essentially contained in several posts here.
What I did was to use listview.setItemChecked(pos, true);, something that I tried before and failed, because it showed nothing. The problem was that LinearLayout does not implement Checkable. So I implemented it (code found essentially somewhere here also) with this:
public class CheckableLinearLayout extends LinearLayout implements Checkable {
private boolean isChecked;
public CheckableLinearLayout(Context context) {
super(context);
}
public CheckableLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setChecked(boolean isChecked) {
this.isChecked = isChecked;
TextView list1 = ((TextView) findViewById(R.id.list1));
TextView desc1 = ((TextView) findViewById(R.id.desc1));
if (isPressed()) {
list1.setTextColor(getResources().getColor(R.color.selected));
setBackgroundColor(getResources().getColor(R.color.pressed));
} else {
setBackgroundColor(getResources().getColor(R.color.black));
if (isChecked) {
list1.setTextColor(getResources().getColor(R.color.checked));
desc1.setTextColor(getResources().getColor(R.color.white));
} else {
list1.setTextColor(getResources().getColor(R.color.normal));
desc1.setTextColor(getResources().getColor(R.color.lesswhite));
}
}
}
public boolean isChecked() {
return isChecked;
}
public void toggle() {
setChecked(!isChecked);
}
}
And that's it. No need to use selector, nor to touch the adapter. To make this work, in the row layout, use <com.[yourpackage].CheckableLinearLayout instead of the usual LinearLayout.

GWT GUI Design - Layout Panels

i have planned to design my application with following areas:
Toolbar Area (fix size)
Workspace Header (for some information or filter criteria's, no fix size)
Workspace Area (should use all available screen space)
Statusbar Area (fix size)
Please see also the attached screenshot.
To use a datagrid which automatically resizes with the screensize, I have read that the best way
is to use the DockLayoutPanel.
I have also read following article:
https://developers.google.com/web-toolkit/doc/latest/DevGuideUiPanels
In this article it's also recommended to use Layout Panels(not only LayoutPanel.class but all Layout Panels) for better standard mode support.
Therefore I have build a very easy example to test the Layout Panels.
Here the simple screenshot example how I tried to design the application:
--- css file --
.test {
margin: 4px;
padding: 20px;
background-color: Lime;
}
package com.test.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.cellview.client.DataGrid;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.SimpleLayoutPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.view.client.ListDataProvider;
public class GwtTestDockFilled implements EntryPoint {
private SimpleLayoutPanel slpToolbarPlaceholder;
private SimpleLayoutPanel slpWorkspacePlaceholder;
private SimpleLayoutPanel slpWorkspaceHeaderPlaceholder;
private SimpleLayoutPanel slpStatusbarPlaceholder;
private LayoutPanel layoutPanel;
private FlowPanel flowPanel;
private Button btnBack;
private Button btnNew;
private Label lblName;
private TextBox txtName;
private DisclosurePanel disclosurePanel;
private LayoutPanel layoutPanel_1;
private Label lblStreet;
private TextBox txtStreet;
private DataGrid<Person> dataGrid;
private TextColumn<Person> colName;
private TextColumn<Person> colStreet;
private DockLayoutPanel dockLayoutPanel_1;
private LayoutPanel layoutPanel_2;
private SimplePager simplePager;
private ListDataProvider<Person> dataProvider = new ListDataProvider<GwtTestDockFilled.Person>();
private static final String[] NAMES = {
"Mary", "Patricia", "Linda", "Barbara", "Elizabeth", "Jennifer", "Maria",
"Susan", "Margaret", "Dorothy", "Lisa", "Nancy", "Karen", "Betty",
"Helen", "Sandra", "Donna", "Carol", "Ruth", "Sharon", "Michelle",
"Laura", "Sarah", "Kimberly", "Deborah", "Jessica", "Shirley", "Cynthia",
"Angela", "Melissa", "Brenda", "Amy", "Anna", "Rebecca", "Virginia",
"Kathleen", "Pamela", "Martha", "Debra", "Amanda", "Stephanie", "Carolyn",
"Christine", "Marie", "Janet", "Catherine", "Frances", "Ann", "Joyce",
"Diane", "Alice", "Julie", "Heather", "Teresa", "Doris", "Gloria",
"Evelyn", "Jean", "Cheryl", "Mildred", "Katherine", "Joan", "Ashley",
"Judith", "Rose", "Janice", "Kelly", "Nicole", "Judy", "Christina",
"Kathy", "Theresa", "Beverly", "Denise", "Tammy", "Irene", "Jane", "Lori"};
/**
* This is the entry point method.
*/
public void onModuleLoad() {
RootLayoutPanel rootLayoutPanel = RootLayoutPanel.get();
DockLayoutPanel dockLayoutPanel = new DockLayoutPanel(Unit.PX);
rootLayoutPanel.add(dockLayoutPanel);
dockLayoutPanel.addNorth(getSlpToolbarPlaceholder(), 60.0);
dockLayoutPanel.addNorth(getSimpleLayoutPanel_2(), 60.0);
dockLayoutPanel.addSouth(getSimpleLayoutPanel_3(), 60.0);
dockLayoutPanel.add(getSlpWorkspacePlaceholder());
fillDataGrid();
}
private void fillDataGrid(){
for(int i = 0; i < NAMES.length; i++){
Person p = new Person();
p.setName(NAMES[i]);
p.setStreet("Spring Road");
dataProvider.getList().add(p);
}
}
private SimpleLayoutPanel getSlpToolbarPlaceholder() {
if (slpToolbarPlaceholder == null) {
slpToolbarPlaceholder = new SimpleLayoutPanel();
slpToolbarPlaceholder.setWidget(getFlowPanel());
}
return slpToolbarPlaceholder;
}
private SimpleLayoutPanel getSlpWorkspacePlaceholder() {
if (slpWorkspacePlaceholder == null) {
slpWorkspacePlaceholder = new SimpleLayoutPanel();
slpWorkspacePlaceholder.setWidget(getDockLayoutPanel_1());
}
return slpWorkspacePlaceholder;
}
private SimpleLayoutPanel getSimpleLayoutPanel_2() {
if (slpWorkspaceHeaderPlaceholder == null) {
slpWorkspaceHeaderPlaceholder = new SimpleLayoutPanel();
slpWorkspaceHeaderPlaceholder.setStyleName("test");
slpWorkspaceHeaderPlaceholder.setWidget(getLayoutPanel());
}
return slpWorkspaceHeaderPlaceholder;
}
private SimpleLayoutPanel getSimpleLayoutPanel_3() {
if (slpStatusbarPlaceholder == null) {
slpStatusbarPlaceholder = new SimpleLayoutPanel();
}
return slpStatusbarPlaceholder;
}
private LayoutPanel getLayoutPanel() {
if (layoutPanel == null) {
layoutPanel = new LayoutPanel();
layoutPanel.add(getLblName());
layoutPanel.setWidgetLeftWidth(getLblName(), 0.0, Unit.PX, 56.0, Unit.PX);
layoutPanel.setWidgetTopHeight(getLblName(), 0.0, Unit.PX, 16.0, Unit.PX);
layoutPanel.add(getTxtName());
layoutPanel.setWidgetLeftWidth(getTxtName(), 62.0, Unit.PX, 165.0, Unit.PX);
layoutPanel.setWidgetTopHeight(getTxtName(), 0.0, Unit.PX, 25.0, Unit.PX);
layoutPanel.add(getDisclosurePanel());
layoutPanel.setWidgetLeftWidth(getDisclosurePanel(), 0.0, Unit.PX, 250.0, Unit.PX);
layoutPanel.setWidgetTopHeight(getDisclosurePanel(), 31.0, Unit.PX, 200.0, Unit.PX);
}
return layoutPanel;
}
private FlowPanel getFlowPanel() {
if (flowPanel == null) {
flowPanel = new FlowPanel();
flowPanel.add(getBtnBack());
flowPanel.add(getBtnNew());
}
return flowPanel;
}
private Button getBtnBack() {
if (btnBack == null) {
btnBack = new Button("New button");
btnBack.setText("Back");
btnBack.setSize("50px", "50px");
}
return btnBack;
}
private Button getBtnNew() {
if (btnNew == null) {
btnNew = new Button("New button");
btnNew.setSize("50px", "50px");
btnNew.setText("New");
}
return btnNew;
}
private Label getLblName() {
if (lblName == null) {
lblName = new Label("Name");
}
return lblName;
}
private TextBox getTxtName() {
if (txtName == null) {
txtName = new TextBox();
}
return txtName;
}
private DisclosurePanel getDisclosurePanel() {
if (disclosurePanel == null) {
disclosurePanel = new DisclosurePanel("Additional Details", false);
disclosurePanel.setAnimationEnabled(true);
disclosurePanel.setContent(getLayoutPanel_1());
}
return disclosurePanel;
}
private LayoutPanel getLayoutPanel_1() {
if (layoutPanel_1 == null) {
layoutPanel_1 = new LayoutPanel();
layoutPanel_1.setSize("5cm", "60px");
layoutPanel_1.add(getTxtStreet());
layoutPanel_1.setWidgetLeftWidth(getTxtStreet(), 60.0, Unit.PX, 91.0, Unit.PX);
layoutPanel_1.setWidgetTopHeight(getTxtStreet(), 0.0, Unit.PX, 32.0, Unit.PX);
layoutPanel_1.add(getLblStreet());
layoutPanel_1.setWidgetLeftWidth(getLblStreet(), 0.0, Unit.PX, 56.0, Unit.PX);
layoutPanel_1.setWidgetTopHeight(getLblStreet(), 0.0, Unit.PX, 22.0, Unit.PX);
}
return layoutPanel_1;
}
private Label getLblStreet() {
if (lblStreet == null) {
lblStreet = new Label("Street");
}
return lblStreet;
}
private TextBox getTxtStreet() {
if (txtStreet == null) {
txtStreet = new TextBox();
}
return txtStreet;
}
private DataGrid<Person> getDataGrid() {
if (dataGrid == null) {
dataGrid = new DataGrid<Person>();
dataProvider.addDataDisplay(dataGrid);
dataGrid.addColumn(getColName(), "Name");
dataGrid.addColumn(getColStreet(), "Street");
}
return dataGrid;
}
private TextColumn<Person> getColName() {
if (colName == null) {
colName = new TextColumn<Person>() {
#Override
public String getValue(Person object) {
return object.getName();
}
};
}
return colName;
}
private TextColumn<Person> getColStreet() {
if (colStreet == null) {
colStreet = new TextColumn<Person>() {
#Override
public String getValue(Person object) {
return object.getStreet();
}
};
}
return colStreet;
}
private DockLayoutPanel getDockLayoutPanel_1() {
if (dockLayoutPanel_1 == null) {
dockLayoutPanel_1 = new DockLayoutPanel(Unit.PX);
dockLayoutPanel_1.addSouth(getLayoutPanel_2(), 40.0);
dockLayoutPanel_1.add(getDataGrid());
}
return dockLayoutPanel_1;
}
private LayoutPanel getLayoutPanel_2() {
if (layoutPanel_2 == null) {
layoutPanel_2 = new LayoutPanel();
layoutPanel_2.add(getSimplePager());
layoutPanel_2.setWidgetLeftWidth(getSimplePager(), 50.0, Unit.PCT, 50.0, Unit.PCT);
}
return layoutPanel_2;
}
private SimplePager getSimplePager() {
if (simplePager == null) {
simplePager = new SimplePager();
simplePager.setDisplay(getDataGrid());
}
return simplePager;
}
public class Person{
private String name;
private String street;
public Person(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
}
}
In this example I have some problems:
The css padding is not working for SimpleLayoutPanel. Which class should I use instead?
I want to use the css padding in the empty skeleton class because if I replace the widgets for
this place holder, it should automatically use the css of the parent (slpWorkspaceHeaderPlaceholder).
I don't want to set the padding for every child of slpWorkspaceHeaderPlaceholder.
The Disclosure Panel for "Additional Details" is not working. The reason is because the DockLayoutPanel
has a fix size for North. I tested a solution that I just change the North size if I open the Disclosure Pane but
the animation is not working.
Is this the right track to design my application or should I use different Panels?
Should I try to use only Layout Panels (better standard-mode support) or are there special cases for other Panels?
Try to use as few types of widgets as possible to minimize your compiled code size, speed up compilation, and improve performance. Your design needs only 3 types of panels (in addition to RootLayoutPanel). You definitely DO NOT need to use a LayoutPanel for your toolbar or workspace header.
Something like this:
FlowPanel toolbar = new FlowPanel();
FlowPanel center = new FlowPanel();
DisclosurePanel workspace = new DisclosurePanel();
workspace.addCloseHandler(new CloseHandler<DisclosurePanel>() {
#Override
public void onClose(CloseEvent<DisclosurePanel> event) {
resize();
}
});
workspace.addOpenHandler(new OpenHandler<DisclosurePanel>() {
#Override
public void onOpen(OpenEvent<DisclosurePanel> event) {
resize();
}
});
Window.addResizeHandler(new ResizeHandler() {
#Override
public void onResize(ResizeEvent event) {
resize();
}
});
DataGrid<Person> dataGrid = new DataGrid<Person>();
FlowPanel statusBar = new FlowPanel();
center.add(workspace);
center.add(dataGrid);
LayoutPanel myPage = new LayoutPanel();
myPage.add(toolbar);
myPage.add(center);
myPage.add(statusBar);
myPage.setWidgetTopHeight(toolbar, 0, Unit.PX, 60, Unit.PX);
myPage.setWidgetTopBottom(center, 60, Unit.PX, 60, Unit.PX);
myPage.setWidgetBottomHeight(statusBar, 0, Unit.PX, 60, Unit.PX);
// add myPage to RootLayoutPanel
resize();
Create resize() method:
private void resize() {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
#Override
public void execute() {
dataGrid.setHeight(center.getOffsetHeight() - workspace.getOffsetHeight() + "px");
}
});
}
Add buttons directly to toolbar widget with the following style:
.myToolbarButton {
width: 50px;
height: 50px;
float: left;
margin: 5px 10px 5px 0;
}
If you style your toolbar (background color), add the following to its style:
overflow: hidden;
HTML has terrible support for "let this component take up as much space as it needs, and then let this component take up the rest of the space." As far as I know, the best way to do what you want in GWT would be to use all LayoutPanels, override onResize, and then do the mathematical layout calculations yourself:
int heightOfEntireCenter = centerPanel.getClientHeight();
int heightOfToolbar = toolBar.getClientHeight();
workspace.setHeight(heightOfEntireCenter - heightOfToolbar);
You'll have to do a ton of finagling, hunting for mysterious layout errors, be annoyed by lag when sizes change, etc. HTML is just bad at this.
If you can fix the size of the toolbar, you'll have a much easier time. I don't even mean that the toolbar can't change size - I just mean you'll have to calculate that and set it. Then the workspace can fill the rest of the space much more naturally. LayoutPanels are the correct choice here, too.

GWT: In a TabLayoutPanel, how do I make tabs wrap if there are too many?

I'm using GWT 2.4. I have a TabLayoutPanel to which I add tabs. Each tab contains a ScrollPanel. My question is, how do I make the tabs in the tab bar wrap to the next line if the width of the tab bar exceeds the visible width?
Thanks, - Dave
GWT's TabLayoutPanel intentionally never wraps tabs. See lines 246-248 in TabLayoutPanel.java - (line 217 defines private static final int BIG_ENOUGH_TO_NOT_WRAP = 16384). You might be able to override this, but as #milan says, it's probably not good design.
Having multiple lines is, indeed, not recommended...
However, to be able to navigate left/right on a single tab bar with many tabs, you can use this recipe:
http://devnotesblog.wordpress.com/2010/06/17/scrollable-gwt-tablayoutpanel/
And an updated implementation that doesn't use the deprecated DeferredCommand:
package whatever.you.want;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.TabLayoutPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* A {#link TabLayoutPanel} that shows scroll buttons if necessary
*/
public class ScrolledTabLayoutPanel extends TabLayoutPanel {
private static final int IMAGE_PADDING_PIXELS = 4;
private LayoutPanel panel;
private FlowPanel tabBar;
private Image scrollLeftButton;
private Image scrollRightButton;
private HandlerRegistration windowResizeHandler;
private ImageResource leftArrowImage;
private ImageResource rightArrowImage;
public ScrolledTabLayoutPanel(double barHeight, Unit barUnit,
ImageResource leftArrowImage, ImageResource rightArrowImage) {
super(barHeight, barUnit);
this.leftArrowImage = leftArrowImage;
this.rightArrowImage = rightArrowImage;
// The main widget wrapped by this composite, which is a LayoutPanel with the tab bar & the tab content
panel = (LayoutPanel) getWidget();
// Find the tab bar, which is the first flow panel in the LayoutPanel
for (int i = 0; i < panel.getWidgetCount(); ++i) {
Widget widget = panel.getWidget(i);
if (widget instanceof FlowPanel) {
tabBar = (FlowPanel) widget;
break; // tab bar found
}
}
initScrollButtons();
}
#Override
public void add(Widget child, Widget tab) {
super.add(child, tab);
checkIfScrollButtonsNecessary();
}
#Override
public boolean remove(Widget w) {
boolean b = super.remove(w);
checkIfScrollButtonsNecessary();
return b;
}
#Override
protected void onLoad() {
super.onLoad();
if (windowResizeHandler == null) {
windowResizeHandler = Window.addResizeHandler(new ResizeHandler() {
#Override
public void onResize(ResizeEvent event) {
checkIfScrollButtonsNecessary();
}
});
}
}
#Override
protected void onUnload() {
super.onUnload();
if (windowResizeHandler != null) {
windowResizeHandler.removeHandler();
windowResizeHandler = null;
}
}
private ClickHandler createScrollClickHandler(final int diff) {
return new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
Widget lastTab = getLastTab();
if (lastTab == null)
return;
int newLeft = parsePosition(tabBar.getElement().getStyle().getLeft()) + diff;
int rightOfLastTab = getRightOfWidget(lastTab);
// Prevent scrolling the last tab too far away form the right border,
// or the first tab further than the left border position
if (newLeft <= 0 && (getTabBarWidth() - newLeft < (rightOfLastTab + 20))) {
scrollTo(newLeft);
}
}
};
}
/** Create and attach the scroll button images with a click handler */
private void initScrollButtons() {
scrollLeftButton = new Image(leftArrowImage);
int leftImageWidth = scrollLeftButton.getWidth();
panel.insert(scrollLeftButton, 0);
panel.setWidgetLeftWidth(scrollLeftButton, 0, Unit.PX, leftImageWidth, Unit.PX);
panel.setWidgetTopHeight(scrollLeftButton, 0, Unit.PX, scrollLeftButton.getWidth(), Unit.PX);
scrollLeftButton.addClickHandler(createScrollClickHandler(+20));
scrollLeftButton.setVisible(false);
scrollRightButton = new Image(rightArrowImage);
panel.insert(scrollRightButton, 0);
panel.setWidgetLeftWidth(scrollRightButton, leftImageWidth + IMAGE_PADDING_PIXELS, Unit.PX, scrollRightButton.getWidth(), Unit.PX);
panel.setWidgetTopHeight(scrollRightButton, 0, Unit.PX, scrollRightButton.getHeight(), Unit.PX);
scrollRightButton.addClickHandler(createScrollClickHandler(-20));
scrollRightButton.setVisible(false);
}
private void checkIfScrollButtonsNecessary() {
// Defer size calculations until sizes are available, when calculating immediately after
// add(), all size methods return zero
Scheduler.get().scheduleDeferred( new Scheduler.ScheduledCommand() {
#Override
public void execute() {
boolean isScrolling = isScrollingNecessary();
// When the scroll buttons are being hidden, reset the scroll position to zero to
// make sure no tabs are still out of sight
if (scrollRightButton.isVisible() && !isScrolling) {
resetScrollPosition();
}
scrollRightButton.setVisible(isScrolling);
scrollLeftButton.setVisible(isScrolling);
}
}
);
}
private void resetScrollPosition() {
scrollTo(0);
}
private void scrollTo(int pos) {
tabBar.getElement().getStyle().setLeft(pos, Unit.PX);
}
private boolean isScrollingNecessary() {
Widget lastTab = getLastTab();
if (lastTab == null)
return false;
return getRightOfWidget(lastTab) > getTabBarWidth();
}
private int getRightOfWidget(Widget widget) {
return widget.getElement().getOffsetLeft() + widget.getElement().getOffsetWidth();
}
private int getTabBarWidth() {
return tabBar.getElement().getParentElement().getClientWidth();
}
private Widget getLastTab() {
if (tabBar.getWidgetCount() == 0)
return null;
return tabBar.getWidget(tabBar.getWidgetCount() - 1);
}
private static int parsePosition(String positionString) {
int position;
try {
for (int i = 0; i < positionString.length(); i++) {
char c = positionString.charAt(i);
if (c != '-' && !(c >= '0' && c <= '9')) {
positionString = positionString.substring(0, i);
}
}
position = Integer.parseInt(positionString);
} catch (NumberFormatException ex) {
position = 0;
}
return position;
}
}