Situation:-
In my code I have to use the LWUIT Component object for the listview controls. The controls are dynamic and hence can be in any number.
Right now I am creating Component objects according to the controls(in numbers) i.e.- for every control to be created first the Component object is creating.
This process slows down the rendering of the listview when the controls are increasing.
Solution:-
If I create the Component object and use it in a loop for all the controls it is taking the reference of the object and hence displays all the listview items(controls) with the same data.
Now I am able to think of one last option of Cloning my object and using it to create the controls.
Problem:-
But I can't find any way in LWUIT by which I can achieve the copying of object.
What can be the alternatives in LWUIT to solve this problem?
P.S.-The listview items are of same type, but with different data.
Use a List component and the Renderer design pattern to create a "rubber stamp" component where you can display a large number of elements easily. See an explanation of this in the Codename One blog.
Create these classes first :
public class ListUtil {
private Vector data = new Vector();
private Content[] contents;
public ListUtil(Vector vData)
{
data = vData;
contents = new Content[vData.size()];
}
public List createList(Display display, CListCell renderer, ActionListener listener)
{
CList theList;
for(int i = 0; i < data.size(); i++)
{
contents[i] = new Content(String.valueOf(data.elementAt(i)));
}
theList = new CList(display, contents, renderer, listener);
return theList;
}
}
public class Content
{
private String row;
public Content(String row)
{
this.row = row;
}
public String getRow()
{
return (row);
}
}
public class CListCell extends Container implements ListCellRenderer {
private Label focus = new Label("");
public CListCell()
{
super();
// create and add the components here among the components which will display data
}
public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected)
{
Content entry = null;
if (value instanceof Content)
entry = (Content)value;
componentDisplayingDataAddedIntoThisListCellRenderer.setText(entry.getRow());
return this;
}
public Component getListFocusComponent(List arg0)
{
return focus;
}
}
public class CList extends List {
private Display disp;
public CList(Display display, Object[] data, CListCell renderer, ActionListener actionListener)
{
super(data);
setListCellRenderer(renderer);
setIgnoreFocusComponentWhenUnfocused(true);
addActionListener(actionListener);
setScrollAnimationSpeed(getScrollAnimationSpeed()/4);
disp = display;
}
public void pointerReleased(int x,int y)
{
if (isEnabled() && hasFocus())
super.pointerReleased(x, y);
}
public void keyReleased(int keyCode)
{
if (isEnabled() && hasFocus())
{
if (disp.getGameAction(keyCode) == Display.GAME_FIRE)
pointerReleased(getX(),getY());
else
super.keyReleased(keyCode);
}
}
}
To create your List and add it to a Form :
public class myForm extends Form implements ActionListener
{
private Vector listSource = // your vector of data
private CListCell renderer = new CListCell();
private List theList = (new ListUtil(listSource)).createList(Display.getInstance(),renderer, this);
...
public void actionPerformed(ActionEvent evt)
{
if (evt.getSource() == theList)
doSomething();
}
}
Related
I am trying to create an ObservableList() to use with my Tableview. The StringData type is a class containing two SimpleStringProperty var. I want to create this list and bind each variable to an specific position of a List. Something like this:
public class DownloadService implements Runnable {
//List that will be updated
public List<SimpleStringProperty> dList = new ArrayList<SimpleStringProperty>();
public class MainScreenController implements Initializable {
//List that populates TV
private ObservableList<DataString> data = FXCollections.observableArrayList();
//tableview
#FXML
private TableView<DataString> tbl_table;
DownloadService download;
...}
public class DataString{
public final SimpleStringProperty state;
public final SimpleStringProperty sinc;
public SimpleStringProperty stateProperty() {
return state;
}
public void setState(String status) {
state.set(status);
}
public SimpleStringProperty sincProperty() {
return sinc;
}
public void setSinc(String sinc) {
this.sinc.set(sinc);
}
}
On MainScreenController I try to do this:
DataString s = new DataString();
s.state.bind (download.dList.get(data.size()));
s.sinc.bind (download.dList.get(data.size()));
data.add(s);
tbl_table.setItems(data);
However, I cannot update the content of data when I update the list on DownloadService. I believe it should update the value of the column associated with the state and sinc variable everytime DownloadService updated the content of the list in each position. I am doing something wrong or is there another way to bind a StringProperty to a position on the list?
Thanks!
You are binding to the specific object inside the list, not to the position. If using SimpleStringProperty in dList isn't strict requirement, than you can use Bindings.stringValueAt():
StringBinding binding = Bindings.stringValueAt(dList, index);
s.state.bind(binding);
If you really need SimpleStringProperty, you can implement custom StringBinding, something like this:
class CustomStringBinding extends StringBinding {
private ObservableList<SimpleStringProperty> op;
private int index;
public CustomStringBinding(ObservableList<SimpleStringProperty> list, int index) {
this.op = list;
this.index = index;
super.bind(op, op.get(index));
}
#Override
public void dispose() {
super.unbind(op, op.get(index));
}
#Override
protected String computeValue() {
try {
return op.get(index).get();
} catch (IndexOutOfBoundsException ex) {
// log
}
return null;
}
#Override
public ObservableList<?> getDependencies() {
return FXCollections.singletonObservableList(op);
}
}
I am learning custom views in android.
I made one custom view, with a rectangle and a text. The code of the custom view is:
public class TileGenerator extends View {
// rectangle parameters
private Rect rect = new Rect();
private Paint rectPaint = new Paint();
private Integer rectEndX;
private Integer rectEndY;
private Integer rectStartX;
private Integer rectStartY;
private Integer rectFillColor;
private Float rectTextStartX;
private Float rectTextStartY;
//rectangle text
private String rectText;
private Paint rectTextPaint = new Paint();
public TileGenerator(Context context) {
super(context);
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void setTileTitleText(String rectText) {
this.rectText = rectText;
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
rectEndX = getrectEndX();
rectEndY = getrectEndY();
rectStartX = getRectStartX();
rectStartY = getRectStartY();
rectTextStartX = rectEndX/4f + rectStartX;
rectTextStartY = 3.5f * rectEndY/4f + rectStartY;
rectTextPaint.setTextSize(rectEndY/8);
rectTextPaint.setColor(Color.BLACK);
rect.set(rectStartX,rectStartY,rectEndX,rectEndY);
rectPaint.setColor(getRectFillColor());
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(rect, rectPaint);
canvas.drawText(rectText,rectTextStartX,rectTextStartY,rectTextPaint );
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
public Integer getrectEndX() {
return rectEndX;
}
public void setrectEndX(Integer rectEndX) {
this.rectEndX = rectEndX;
}
public Integer getrectEndY() {
return rectEndY;
}
public void setrectEndY(Integer rectEndY) {
this.rectEndY = rectEndY;
}
public Integer getRectStartX() {
return rectStartX;
}
public void setRectStartX(Integer rectStartX) {
this.rectStartX = rectStartX;
}
public Integer getRectStartY() {
return rectStartY;
}
public void setRectStartY(Integer rectStartY) {
this.rectStartY = rectStartY;
}
public Integer getRectFillColor() {
return rectFillColor;
}
public void setRectFillColor(Integer rectFillColor) {
this.rectFillColor = rectFillColor;
}
public String getRectText() {
return rectText;
}
}
After that I created an blank activity. I am doing all with JAVA code. No XML. Then I try to add above custom view to a gridview layout. I want to add two custom views with different text in a horizontal gridview. So far my code is as below:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GridLayout gridLayout = new GridLayout(this);
// first custom view
CustomRectWithText customRectWithText = new CustomRectWithText(this);
customRectWithText.setRectEndX(200);
customRectWithText.setRectEndY(200);
customRectWithText.setRectStartX(2);
customRectWithText.setRectStartY(2);
customRectWithText.setImage(image);
customRectWithText.setRectText("Text");
customRectWithText.setRectFillColor(Color.BLUE);
gridLayout.addView(customRectWithText);
// second custom view
CustomRectWithText customRectWithText1 = new CustomRectWithText(this);
customRectWithText1.setRectEndX(400);
customRectWithText1.setRectEndY(200);
customRectWithText1.setRectStartX(200 + 5);
customRectWithText1.setRectStartY(2);
customRectWithText1.setTileTitleText("Text 1");
customRectWithText1.setRectFillColor(Color.GREEN);
gridLayout.addView(customRectWithText1);
setContentView(gridLayout);
}
But still I am not getting both of the rectangles in a grid view. Only one rectangle is displayed at a time. In above case only first custom view is displayed.
Where am I doing wrong.
All I want is to make a repetitive rectangle of varying labels of any size inside a grid view.
Is this the way to do it. I mean is there any other way around.
I dont want to use ListItems.
Sorry but i do not have enough repo to comment.
But why dont you make an adapter?
Gridview behaves same as listView.
Use adapter to fill your grid.
This is the proper way to populate listView and gridView also.
I'm experiencing with the UICollectionView(Controller) for the first time. Actually it should be as simple as working with TableViews, it's not though.
Rather than showing all images in a flow (several rows) just the top row is displayed. All the other images are somewhere... scrolling is enabled but nothing happens, no bouncing, no scrolling, ...
And after an orientation change (and back) some more images are visible but they appear randomly. After every orientation change they appear at an other location.
My example should have 7 images.
My properties in IB:
First time:
After rotating (and back):
And my source code to implement the photo gallery.
using System;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using System.Collections.Generic;
using Xamarin.Media;
using MonoTouch.AssetsLibrary;
using MonoTouch.CoreGraphics;
using System.Diagnostics;
using System.Linq;
using System.Drawing;
namespace B2.Device.iOS
{
public partial class TagesRapportDetailRegieBilderCollectionViewController : UICollectionViewController
{
private const string Album = "Rapport";
public TagesRapportDetailRegieBilderSource Source { get; private set; }
private TagesRapportDetailRegieBilderDelegate _delegate;
public TagesRapportDetailRegieBilderCollectionViewController (IntPtr handle) : base (handle)
{
Source = new TagesRapportDetailRegieBilderSource(this);
_delegate = new TagesRapportDetailRegieBilderDelegate(this);
// Delegate - Muss im konstruktor sein. ViewDidLoad geht nicht!
CollectionView.Delegate = _delegate;
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Cell Klasse registrieren
CollectionView.RegisterClassForCell (typeof(ImageCell), new NSString("imageCell"));
// DataSource
CollectionView.Source = Source;
// Bilder laden
LoadImages();
}
private void LoadImages()
{
Source.Images.Clear();
var assetsLibrary = new ALAssetsLibrary();
assetsLibrary.Enumerate(ALAssetsGroupType.Album, GroupsEnumeration, GroupsEnumerationFailure);
}
private void GroupsEnumeration(ALAssetsGroup group, ref bool stop)
{
if (group != null && group.Name == Album)
{
//notifies the library to keep retrieving groups
stop = false;
//set here what types of assets we want,
//photos, videos or both
group.SetAssetsFilter(ALAssetsFilter.AllPhotos);
//start the asset enumeration
//with ALAssetsGroup's Enumerate method
group.Enumerate(AssetsEnumeration);
CollectionView.ReloadData();
}
}
private void AssetsEnumeration(ALAsset asset, int index, ref bool stop)
{
if (asset != null)
{
//notifies the group to keep retrieving assets
stop = false;
//use the asset here
var image = new UIImage(asset.AspectRatioThumbnail());
Source.Images.Add(image);
}
}
private void GroupsEnumerationFailure(NSError error)
{
if (error != null)
{
new UIAlertView("Zugriff verweigert", error.LocalizedDescription, null, "Schliessen", null).Show();
}
}
}
public class TagesRapportDetailRegieBilderDelegate : UICollectionViewDelegateFlowLayout
{
private TagesRapportDetailRegieBilderCollectionViewController _controller;
public TagesRapportDetailRegieBilderDelegate(TagesRapportDetailRegieBilderCollectionViewController controller)
{
_controller = controller;
}
public override System.Drawing.SizeF GetSizeForItem(UICollectionView collectionView, UICollectionViewLayout layout, NSIndexPath indexPath)
{
var size = _controller.Source.Images[indexPath.Row].Size.Width > 0
? _controller.Source.Images[indexPath.Row].Size : new SizeF(100, 100);
size.Width /= 3;
size.Height /= 3;
return size;
}
public override UIEdgeInsets GetInsetForSection(UICollectionView collectionView, UICollectionViewLayout layout, int section)
{
return new UIEdgeInsets(50, 20, 50, 20);
}
}
public class TagesRapportDetailRegieBilderSource : UICollectionViewSource
{
private TagesRapportDetailRegieBilderCollectionViewController _controller;
public List<UIImage> Images { get; set; }
public TagesRapportDetailRegieBilderSource(TagesRapportDetailRegieBilderCollectionViewController controller)
{
_controller = controller;
Images = new List<UIImage>();
}
public override int NumberOfSections(UICollectionView collectionView)
{
return 1;
}
public override int GetItemsCount(UICollectionView collectionView, int section)
{
return Images.Count;
}
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = collectionView.DequeueReusableCell(new NSString("imageCell"), indexPath) as ImageCell;
cell.Image = Images[indexPath.Row];
return cell;
}
}
public class ImageCell : UICollectionViewCell
{
UIImageView imageView;
[Export ("initWithFrame:")]
public ImageCell (System.Drawing.RectangleF frame) : base (frame)
{
imageView = new UIImageView(frame);
imageView.AutoresizingMask = ~UIViewAutoresizing.None;
ContentView.AddSubview (imageView);
}
public UIImage Image
{
set
{
imageView.Image = value;
}
}
}
}
If all you want to do is displaying the cells in an uniform grid, overriding GetSizeForItem shouldn't be necessary in the first place. Just configure the cellsize property of your flow layout either in IB or programatically during ViewDidLoad and be done with it.
There's another problem with your code:
group.Enumerate(AssetsEnumeration)
This will run asynchronously. That means:
CollectionView.ReloadData();
Will only cover a small subset of your images. It would be better to issue a ReloadData() when group == null, which indicates that the enumeration is complete.
You could also avoid ReloadData alltogether and call CollectionView.InsertItem() everytime you've added an image. This has the benefit of your items become immediately visible instead of all of them at once after everything has been enumerated - which may take some time (on the device). The downside is that you've got to be careful to not run into this.
I have a UIPickerView that is using a custom UIPickerViewModel to overide the view that is shown in the UIPicker View.
When I First open the UIPickerView, all goes well, I am able to select an item and what not.
If the Item selected is the first or 2nd item in the UIPicker, I don't run into any issues. However, if I select say the 3rd item, dismiss the Picker then try to select another option again, the UIPickerView never shows on the screen.
What I found out is that the GetView method on my PickerViewModel is getting called over and over again for the selected row as well as the row after. Is there anything I have done that might cause this behavior? Below is my PickerViewModel
public class AddressPickerViewModel : UIPickerViewModel
{
private ShippingView view;
public AddressPickerViewModel(ShippingView view)
{
this.view = view;
}
private AddressPickerCell currentSelectedCell;
public override UIView GetView(UIPickerView picker, int row, int component, UIView view)
{
if (view == null)
{
view = new AddressPickerCell();
var views = NSBundle.MainBundle.LoadNib(AddressPickerCell.Key, view, null);
view = Runtime.GetNSObject(views.ValueAt(0)) as AddressPickerCell;
}
var pickerCell = (AddressPickerCell)view;
pickerCell.Selected = false;
pickerCell.ShippingAddress = this.view.ViewModel.Addresses.ToArray()[row].Address;
return view;
}
public override int GetComponentCount(UIPickerView v)
{
return 1;
}
public override int GetRowsInComponent(UIPickerView pickerView, int component)
{
return this.view.ViewModel.Addresses.Count;
}
public override void Selected(UIPickerView picker, int row, int component)
{
if (this.currentSelectedCell != null)
{
this.currentSelectedCell.Selected = false;
}
view.ViewModel.SelectedAddress=
this.view.ViewModel.Addresses.ElementAt(picker.SelectedRowInComponent(0));
var cell = picker.ViewFor(row, component) as AddressPickerCell;
if (cell != null)
{
this.currentSelectedCell = cell;
this.currentSelectedCell.Selected = true;
}
}
public override float GetComponentWidth(UIPickerView picker, int component)
{
if (component == 0)
return 300f;
else
return 40f;
}
public override float GetRowHeight(UIPickerView picker, int component)
{
return 140f;
}
}
Am a little stuck with getting changes reflected from the ViewModel to the View when used in a MvxBindableTableViewCell. I am using the vNext branch of MvvmCross on iOS.
Everything is set up properly and the initial values are visible when loading/showing the list for the first time. The list is a ObservableCollection<T> and the ViewModels inherit from MvxViewModel (thus implements INotifyPropertyChanged).
The main ViewModel looks like this:
public abstract class BaseViewModel : MvxViewModel, IMvxServiceConsumer
{
//... just regular implementation
}
public class UploadListViewModel: BaseViewModel
{
private readonly IUploadItemTasks uploadItemTasks;
private readonly IPhotoPickerService photoPickerService;
public IObservableCollection<UploadItemViewModel> Uploads { get { return this.LoadUploadItems(); } }
public UploadListViewModel()
{
this.uploadItemTasks = this.GetService<IUploadItemTasks>();
this.photoPickerService = this.GetService<IPhotoPickerService>();
}
private IObservableCollection<UploadItemViewModel> LoadUploadItems()
{
using (var unitOfWork = UnitOfWork.Start ())
{
return new SimpleObservableCollection<UploadItemViewModel>(uploadItemTasks.GetAll());
}
}
public void StartUpload ()
{
if (this.Uploads == null || this.Uploads.Count == 0) {
ReportError("Error", "No images to upload");
return;
}
this.Uploads.ForEach (uploadItem => PostCallback (uploadItem));
}
private void PostCallback (UploadItemViewModel uploadAsset)
{
IProgressReporter progressReporter = uploadAsset;
this.photoPickerService.GetAssetFullImage(uploadAsset.ImageUrl,
(image) => {
UIImage fullImage = image;
NSData jpeg = fullImage.AsJPEG();
byte[] jpegBytes = new byte[jpeg.Length];
System.Runtime.InteropServices.Marshal.Copy(jpeg.Bytes, jpegBytes, 0, Convert.ToInt32(jpeg.Length));
MemoryStream stream = new MemoryStream(jpegBytes);
Uri destinationUrl = new Uri(uploadAsset.DestinationUrl + "&name=" + uploadAsset.Name + "&contentType=image%2FJPEG");
//TO DO: Move this to plugin
var uploader = new Uploader().UploadPicture (destinationUrl, stream, UploadComplete, progressReporter);
uploader.Host = uploadAsset.Host;
ThreadPool.QueueUserWorkItem (delegate {
uploader.Upload ();
jpeg = null;
});
});
}
private void UploadComplete (string name)
{
if (name == null){
ReportError("Error","There was an error uploading the media.");
} else
{
//ReportError("Succes", name);
}
}
The item ViewModel looks like:
public interface IProgressReporter
{
float Progress { get; set;}
}
public abstract class BaseAssetViewModel: BaseViewModel, IBaseAssetViewModel
{
//... just regular properties
}
public class UploadItemViewModel: BaseAssetViewModel, IProgressReporter
{
public UploadItemViewModel(): base()
{
}
private float progress;
public float Progress {
get {
return this.progress;
}
set {
this.progress = value;
this.RaisePropertyChanged(() => Progress);
}
}
}
The View for the items inherits from MvxBindableTableViewCell and has the property:
private float progress;
public float ProgressMarker {
get {
return progress;
}
set {
progress = value;
// change progressbar or textfield here
}
}
The tableviewcell is bounded to the UploadItemViewModel via the BindingText:
public const string BindingText = #"ProgressMarker Progress, Converter=Float;";
The Uploader class mentioned in the snippet of UploadListViewModel implements a private method which tries to set the progress on the IProgressReporter.
float progressValue;
void SetProgress (float newvalue)
{
progressValue = newvalue;
this.dispatcher.InvokeOnMainThread (delegate {
if (ProgressReporter != null)
ProgressReporter.Progress = progressValue;
});
}
During the first viewing of the list I can see that the properties in both the ViewModel and View are being hit but when I update the ViewModel via the interface IProgressReporter with a new value in Progress the View in the tableviewcell is not updated nor the property is being called.
What am I doing wrong or what am I missing here?
UPDATE: Check the answer to this question.
I found why the binding didn't work. I was replacing the ObservableCollection over and over again.. I changed that piece of code as stated below and now it reflects the changes made to the UploadItemViewModel in the View of the cell.
private IObservableCollection<UploadItemViewModel> uploads;
private IObservableCollection<UploadItemViewModel> LoadUploadItems()
{
if (uploads == null)
{
using (var unitOfWork = UnitOfWork.Start ())
{
uploads = new SimpleObservableCollection<UploadItemViewModel>(uploadItemTasks.FindAll());
}
}
return uploads;
}