I am using MonoTouch.Dialog in my app; however, the form in question is implemented using a UIViewController to which which I added a TableView (so I can also add a UIToolbar).
I love the ImageLoader that comes in MonoTouch.Dialog.Utilities and am trying to use it in the GetCell() method of the DataSource for the TableView to render an image from a URL.
var callback = new ImageLoaderCallback(controller, cell.ImageView, indexPath); // ImageLoaderCallback implements IImageUpdated
cell.ImageView.Image = ImageLoader.DefaultRequestImage(new Uri(picFV.Value), callback);
The problem is that until the URL is downloaded, the space for the ImageView is collapsed (so that the text to the right of the image is actually anchored on the left of the table).
What I'd like to do is display a temporary local image which has the same dimensions as the downloaded image, so that when the ImageLoader is done retrieving and rendering the image, the user experience isn't as jarring.
I tried to do the following in the GetCell() call... cell.ImageView.Image is set to some other image (not shown below), and then I get a reference to what comes back from ImageLoader.DefaultRequestImage.
var image = ImageLoader.DefaultRequestImage(new Uri(picFV.Value), callback);
callback.Image = image;
the last line stuffs the image reference returned by ImageLoader into the callback's state, and the callback will replace the cell's ImageView.Image when the ImageLoader is done (see below):
// callback class for the MonoTouch.Dialog image loader utility
private class ImageLoaderCallback : IImageUpdated
{
private ListViewController controller;
private UIImageView imageView;
private NSIndexPath indexPath;
public ImageLoaderCallback(ListViewController c, UIImageView view, NSIndexPath path)
{
controller = c;
imageView = view;
indexPath = path;
}
public UIImage Image { get; set; }
void IImageUpdated.UpdatedImage(Uri uri)
{
if (uri == null)
return;
if (Image != null)
imageView.Image = Image;
// refresh the display for the row of the image that just got updated
controller.TableView.ReloadRows(new NSIndexPath [] { indexPath }, UITableViewRowAnimation.None);
}
}
However, this doesn't work because it appears that the ImageLoader needs to be "pulled" by the TableView when it tries to render its ImageView (i.e. the ImageLoader callback never gets invoked because no one is pulling on the URL).
How do I accomplish this scenario?
Thanks!
I know this is an old question now, but I came across it when searching for the same thing and have seen the following blog post which states how to accomplish this. I haven't yet tried it out but hopefully it might help you or anyone else coming across this in the future: http://yusinto.blogspot.co.uk/2012/05/background-image-downloading-with.html
Related
I am trying to insert an image into table view in JavafX. Here is how I set up my table view:
TableColumn prodImageCol = new TableColumn("IMAGES");
prodImageCol.setCellValueFactory(new PropertyValueFactory<Product, Image>("prodImage"));
prodImageCol.setMinWidth(100);
// setting cell factory for product image
prodImageCol.setCellFactory(new Callback<TableColumn<Product,Image>,TableCell<Product,Image>>(){
#Override
public TableCell<Product,Image> call(TableColumn<Product,Image> param) {
TableCell<Product,Image> cell = new TableCell<Product,Image>(){
public void updateItem(Product item, boolean empty) {
if(item!=null){
ImageView imageview = new ImageView();
imageview.setFitHeight(50);
imageview.setFitWidth(50);
imageview.setImage(new Image(product.getImage()));
}
}
};
return cell;
}
});
viewProduct.setEditable(false);
viewProduct.getColumns().addAll(prodImageCol, prodIDCol, prodNameCol, prodDescCol, prodPriceCol, col_action);
viewProduct.getItems().setAll(product.populateProductTable(category));
private SimpleObjectProperty prodImage;
public void setprodImage(Image value) {
prodImageProperty().set(value);
}
public Object getprodImage() {
return prodImageProperty().get();
}
public SimpleObjectProperty prodImageProperty() {
if (prodImage == null) {
prodImage = new SimpleObjectProperty(this, "prodImage");
}
return prodImage;
}
And this is how I retrieve the image from database:
Blob blob = rs.getBlob("productImage");
byte[] data = blob.getBytes(1, (int) blob.length());
bufferedImg = ImageIO.read(new ByteArrayInputStream(data));
image = SwingFXUtils.toFXImage(bufferedImg, null);
However I am getting error at the setting up of table view: imageview.setImage(new Image(product.getImage()));
The error message as:
no suitable constructor found for Image(Image)
constructor Image.Image(String,InputStream,double,double,boolean,boolean,boolean) is not applicable
(actual and formal argument lists differ in length)
constructor Image.Image(int,int) is not applicable
(actual and formal argument lists differ in length)
constructor Image.Image(InputStream,double,double,boolean,boolean) is not applicable
(actual and formal argument lists differ in length)
constructor Image.Image(InputStream) is not applicable
(actual argument Image cannot be converted to InputStream by method invocation conversion)
constructor Image.Image(String,double,double,boolean,boolean,boolean) is not applicable
(actual and formal argument lists differ in length)
constructor Image.Image(String,double,double,boolean,boolean) is not applicab...
I did managed to retrieve and display an image inside an image view but however, I can't display it in table column. Any help would be appreciated. Thanks in advance.
The problem that's causing the exception is that your method product.getImage() is returning an javafx.scene.Image. There's no need to do anything else at this point: You have an image, so use it (before you were trying to construct new Image(Image) - which is not even possible). This is what you want to be using:
imageview.setImage(product.getImage());
Your second problem is that while you're creating an ImageView every time you update the cell, you're not doing anything with it. Here's your original code:
TableCell<Product,Image> cell = new TableCell<Product,Image>(){
public void updateItem(Product item, boolean empty) {
if(item!=null){
ImageView imageview = new ImageView();
imageview.setFitHeight(50);
imageview.setFitWidth(50);
imageview.setImage(new Image(product.getImage()));
}
}
};
return cell;
Like #tomsontom suggested, I'd recommend using setGraphic(Node) to attach your ImageView to the TableCell. So you might end up with something like this:
//Set up the ImageView
final ImageView imageview = new ImageView();
imageview.setFitHeight(50);
imageview.setFitWidth(50);
//Set up the Table
TableCell<Product,Image> cell = new TableCell<Product,Image>(){
public void updateItem(Product item, boolean empty) {
if(item!=null){
imageview.setImage(product.getImage()); //Change suggested earlier
}
}
};
// Attach the imageview to the cell
cell.setGraphic(imageview)
return cell;
The first point #tomsontom was making is that your method of creating an Image is a little roundabout. Sure, it seems to work... but there's a simpler way. Originally you were using:
bufferedImg = ImageIO.read(new ByteArrayInputStream(data));
image = SwingFXUtils.toFXImage(bufferedImg, null);
But a better way of doing it would be switching those lines with:
image = new Image(new ByteArrayInputStream(data));
why are not creating the Image directly from the data new Image(new ByteArrayInputStream(data)) no need to rewrap it our use Swing stuff
I don't see a public Image(Object) constructor in FX8 - why passing it anyways if you are already have an image instance?
you need to set the ImageView on the cell with setGraphic()
My problem is as follows:
I am creating a section with a number of ImageStringElements that when selected an audio file will play, e.g.
Section s = new Section();
foreach (var idea in ideas)
{
s.Add(new ImageStringElement(idea.Id, delegate {ElementTapped();}, playImage));
}
Now when one of the elements is tapped, I would like to change the playImage to another one, i.e. PauseImage. Then again, when is it selected it changes back to the PlayImage. Not sure how to do this in the ElementTapped() method. Basically I would like to have a similar functionality as in the voice memos app.
You can subclass ImageStringElement and make two changes:
Add:
class FlippingImageElement : ImageStringElement
{
UIImage currentImage;
UITableViewCell currentCell;
public FlippingImageElement (string caption, UIImage image) : base (caption, image)
{
currentImage = image;
}
public override UITableViewCell GetCell (UITableView tv)
{
var cell = base.GetCell (tv);
cell.ImageView.Image = currentImage;
currentCell = cell;
}
public void SetImage (UIImage image)
{
currentImage = image;
if (currentCell != null)
currentCell.ImageView.Image = currentImage;
}
}
Use this new element instead of the MonoTouch.Dialog one, and call the SetImage API to change the image
I am new to Monotouch and attempting to understand how some of the basics hang together. Hopefully someone out there will be able to assist.
I've created a test project in MonoDevelop based on the Multi-Screened Apps tutorial on the Xamarin site and have extended it to include a tableView. I am having issues with referencing the Navigation Controller in a view that I need to push a detail view onto to display the detail of an item tapped in the table via an accessory button. I know some of the coding is scrappy, just been trying to get it working at this stage rather than the clarity in the code! (I'm using the latest versions of all Mono tools/libraries etc and XCode 4 on Lion). Starting at the beginning here's the code in FinishedLaunching in AppDelegate.
window = new UIWindow (UIScreen.MainScreen.Bounds);
this.rootNavigationController = new UINavigationController();
// Create a new homescreen
HomeScreen homeScreen = new HomeScreen();
// Add the homescreen to the root navigation controller
this.rootNavigationController.PushViewController(homeScreen, false);
// Set the root view controller on the window - the navigation controller
// will handle the rest.
this.window.RootViewController = this.rootNavigationController;
// make the window visible
this.window.MakeKeyAndVisible ();
homeScreen just contains a button which loads a new view containing a table view (OrderList). Here's the button event handler.
void HandleOrdersButtonhandleTouchUpInside (object sender, EventArgs e)
{
if (orderListScreen == null)
orderListScreen = new OrderList();
NavigationController.PushViewController(orderListScreen, true);
}
This all works fine. I've got some dummy data that loads into the table view, which also works fine. OrderData is a simple class for testing which just contains a couple of properties. I've added an AccessoryButton to the cells and am trying to load a detail view when this is tapped. Here's the code that does this - comment in code where issue is! (I'd previously tested the AccessoryButtonTapped functionilty was working by just displaying an alert).
public override void AccessoryButtonTapped (UITableView tableView, NSIndexPath indexPath)
{
var dataSource = (OrdersTableViewDataSource)tableView.DataSource;
if (detailScreen == null)
detailScreen = new OrderDetailScreen();
OrderData theOrder = dataSource.OrdersData[indexPath.Row];
detailScreen.currentOrder = theOrder;
// Cant get a reference to NavigationController here to push the detail view!
// this.NavigationController is not available
this.NavigationController.PushViewController(detailScreen, true);
}
My understanding of NavigationControllers from what I've read so far is that this reference should be available through all views that originate from the root ViewController/NavigationController without the need to pass the reference from AppDelegate through the various view constructors?
Can anyone tell me what I might be missing here?
Thanks in advance.
** An update after reviewing Jason's comment: (Please let me know if this is the incorrect way to post updates)
So, I tried the following:
I saved a reference to the NavigationController in the constructor for the ViewController that contains the table view as follows:
public partial class OrderList : UIViewController
{
UINavigationController navController;
public OrderList () : base ("OrderList", null)
{
this.Title = "Orders";
navController = this.NavigationController;
}
Then passed that into the TableViewDelegate, where the AccessoryButtonTapped is handled, in the ViewDidLoad method.
public override void ViewDidLoad ()
{
orderTableView.DataSource = new OrdersTableViewDataSource();
orderTableView.Delegate = new OrdersTableViewDelegate(navController);
base.ViewDidLoad ();
}
Then referenced that in the TableViewDelegate:
public class OrdersTableViewDelegate : UITableViewDelegate
{
UINavigationController navController;
public OrdersTableViewDelegate(UINavigationController controller)
{
navController = controller;
}
// Rest of class definition
}
Then the reference to the NavigationController using navController compiles with the code as previously described using the following in the AccessoryButtonTapped method:
navController.PushViewController(detailScreen, true);
When I run this and tap on the AccessoryButton I get a null reference exception on navController. The reference to this.NavigationController in the ViewController constructor is null. Am I doing something in the wrong place or sequence?
Cheers
The NavigationController property is on your table's view controller. If you are trying to reference it from your table's datasource, you need to pass a reference to the controller when you create the datasource.
Are there any existing extensions or is it fairly straight forward to add styles to RootElement in monotouch.dialog in a similar way you can style StyledStringElement.
Basically I would like to add an image, or badge to RootElement to indicate what sort of details would be in the child view, eg add Success, Warning, Error, Info type image - so the users may only be interested in clicking through to details that are not fully successful.
So ideally I would be able to code something like this...
UIImage imageSuccess = ImageLoader.DefaultRequestImage (new Uri ("file://" + Path.GetFullPath ("Images/Success.png")), null);
var root = new RootElement("Root") {
Image = imageSuccess,
Accessory = UITableViewCellAccessory.DetailDisclosureButton,
new Section (){
new BooleanElement ("Airplane Mode", false),
new RootElement ("Notifications") {
new Section (null, "Turn off Notifications")
{
new BooleanElement ("Notifications", false)
}
}}
};
Thanks for any help or pointers.
This question is old, but if anyone else comes across it you can subclass the RootElement class to add an icon. My code is as follows:
public class ImageRootElement : RootElement
{
private UIImage _image;
public override MonoTouch.UIKit.UITableViewCell GetCell (MonoTouch.UIKit.UITableView tv)
{
var baseCell = base.GetCell (tv);
var cell = new UITableViewCell (UITableViewCellStyle.Subtitle, "cellId");
cell.TextLabel.Text = Caption;
cell.Accessory = baseCell.Accessory;
cell.ImageView.Image = _image;
return cell;
}
public ImageRootElement (string caption, UIImage image) : base(caption)
{
_image = image;
}
}
Since MT.Dialog is open source, you can modify the RootElement properties and constructors however you like. I don't think there's anything that does what you want right out of the box, so you'll have to extend Dialog to meet your needs.
As an aside, it sounds like you MAY be misunderstanding the intent of RootElement. RootElement is simply the main container that all of your sections and elements are in. It doesn't seem to make sense to have a disclosure indicator or badge on a RootElement, simply because that is not the intent of RootElement. It's possible that I could just be misunderstanding you. If, however, you want to do custom styling with badges, etc, on an element, you can create custom element classes that inherit from OwnerDrawnElement, overriding it's two abstract methods. However, read Miguel's answer to a similar question here before doing so.
I have QLPreviewController up and running but I'm using PresentModalViewController() to show the QLPreviewController directly. For reasons beyond explanation, I would like to have my own UIViewController which will create its own view and within that view I would like to use the QLPreviewController. Should be easy I thought, but the code below just does nothing. The QLPreviewControllers ViewDidAppear never gets called. (In my example below, PreviewController inherits from QLPreviewController and encapsulates delegate, preview item and source).
Can somebody explain what is wrong with the code below (besides the fact that it is pointless :-))?
Oh, yeah: in my test scenario, I present the controller below modally. It shows up but witout the preview.
public class OuterPreviewController : UIViewController
{
public OuterPreviewController (QLPreviewControllerDataSource oDataSource) : base()
{
this.oDataSource = oDataSource;
}
private PreviewController oPreviewController;
private QLPreviewControllerDataSource oDataSource;
public override void LoadView ()
{
this.View = new UIView();
this.View.Frame = new RectangleF(0, 0, 500, 500);
this.View.BackgroundColor = UIColor.Red;
}
public override void ViewDidAppear (bool animated)
{
// Code execution comes her. No errors, no issues.
base.ViewDidAppear (animated);
this.oPreviewController = new PreviewController();
this.oPreviewController.DataSource = this.oDataSource;
// Preview controller's view is added but it never shows up.
this.View.AddSubview(this.oPreviewController.View);
this.oPreviewController.View.Frame = this.View.Frame;
this.oPreviewController.View.Center = this.View.Center;
}
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
{
return true;
}
}
Found a solution by coincidence today: all ReloadData() on the preview controller and magically it will show its contents.
This allows to add a QLPreviewController to an existing view as a subview and embed a preview. It also gets you rid of the toolbar which contains the open in menu.