I am trying to figure out how to:
1. use IB, in Xcode 4+ to visually create a custom subclass of UITableViewCell to use in MT.
How to use that custom class as an element in MT.Dialog.
I have searched extensively and haven't found any example or been able to solve it.
Here is the process I have been trying:
Step 1 seems easy enough now that I have found a good tutorial: http://www.arcticmill.com/2012/05/uitableview-with-custom-uitableviewcell.html
Step 2 seems to be where I am stuck. Once I have the new class, with a few labels dropped onto it in this case:
public partial class CustomListCell : UITableViewCell {
public CustomListCell () :base(UITableViewCellStyle.Default,"CellID") {
}
public void UpDateData(string lbl1, string lbl2, string lbl3) {
this.lblLabel1.Text = lbl1;
this.lblLabel2.Text = lbl2;
this.lblLabel3.Text = lbl3;
}
}
I cannot figure out how to turn it into something I can use in MT.Dialog. I have tried :
public partial class CustomListCell :Element
but the label controls don't seem to every be created.No matter where I put a call to UpdateData they are all null, hence a null reference exception, even if the constructor has executed just fine. I've also tried making it an OwnerDrawnElement, but ran into a couple of problems with that.
Is this possible? Is there a recommended pattern?
I think the sample you are looking for is the OwnerDrawnCell: https://github.com/migueldeicaza/MonoTouch.Dialog/blob/master/MonoTouch.Dialog/Elements/OwnerDrawnElement.cs
See how it overrides the GetCell() method to provide a custom cell:
public override UITableViewCell GetCell (UITableView tv)
{
OwnerDrawnCell cell = tv.DequeueReusableCell(this.CellReuseIdentifier) as OwnerDrawnCell;
if (cell == null)
{
cell = new OwnerDrawnCell(this, this.Style, this.CellReuseIdentifier);
}
else
{
cell.Element = this;
}
cell.Update();
return cell;
}
You just need to do the same thing - except you need to replace OwnerDrawnCell with the XIB-loaded cell.
I've also done a blog post on how I load cells from XIBs using the new iOS6 variant of the DequeueReusableCell API - see http://slodge.blogspot.co.uk/2013/01/uitableviewcell-using-xib-editor.html
Related
I'm making a level select screen and I need the text field to display different level numbers for each level. I don't really see what I'm doing wrong here, but I'll go over what I did and post the relevant code.
I have a button class (linked) and inside the symbol I have a dynamic text field. I have two classes of relevance, LevelSelectScreen and LevelSelectButtons (pretty self-explanatory what they are). I thought it would be really easy to change the text if I did it inside the LevelSelectButtons class, by simply doing levelText.text = "Wanted Text", where levelText is the given instance name for my button (just a text field on top of my graphic for the button). Unfortunately, this gives the oh so common and annoying error: TypeError: Error #1009: Cannot access a property or method of a null object reference.
I tried doing virtually the same thing in my LevelSelectScreen class during my loop, but I got the same error. Help on how to get this levelText to work is greatly appreciated! Here is the relevant code.
LevelSelectScreen
public class LevelSelectButtons extends SimpleButton {
public var levelNumber:int;
public var levelSelectScreen:LevelSelectScreen;
public function LevelSelectButtons(i) {
x = 200;
y = 100 + 50*i;
addEventListener(MouseEvent.CLICK,LevelSelectClicked,false,0,true)
levelNumber = i;
levelText.text = "Level" + i;
}
}
LevelSelectScreen
public class LevelSelectScreen extends MovieClip {
public var levelSelectButtons:LevelSelectButtons;
public var mainMenuButton:MainMenuButton;
public function LevelSelectScreen() {
for (var i:int = 1; i<=2; i++)
{
levelSelectButtons = new LevelSelectButtons(i);
addChild(levelSelectButtons);
}
}
}
You can't have a dynamic text field in a SimpleButton.
Annoying, I know.
Simple fix would be to have LevelSelectButton wrap a SimpleButton instead of extend it. Then your text field would be inside LevelSelectButton on top of a text-less SimpleButton. (Be sure to set mouseEnabled to false on the text field so it doesn't interfere with mouse events on the SimpleButton.
A more complex option would be to write your own custom button class.
It's not actually that difficult, but might be overkill for what you're trying to do here.
its because you haven't declared levelText variable and you are trying to access it, therefor Cannot access a property or method of a null object reference.
I'm wondering if I'm doing this the correct way - this method works, but feels somewhat 'dirty'. Essentially, a button in an MvxTableViewCell changes a parameter of the bound object, but the cell does not update to reflect the change until it's scrolled out of view and back into view (ie the cell is 'redrawn'). All the examples here are simplified, but you get the idea..
Firstly, my object:
public class Expense
{
public decimal Amount { get; set; }
public bool Selected { get; set; }
public Command FlipSelected
{
get { return new MvxCommand(()=> this.Selected = !this.Selected); }
}
}
Secondly, my cell (in the constructor) contains:
this.DelayBind(() =>
{
var set = this.CreateBindingSet<HistoryCell, Expense>();
set.Bind(this.TitleText).To(x => x.Amount);
set.Bind(this.SelectButton).To(x=> x.FlipSelected);
set.Bind(this.SelectButton).For(x => x.BackgroundColor).To(x => x.Selected).WithConversion(new ButtonConverter(), null);
set.Apply();
});
And i have a valueconverter that returns the background colour of the button:
class ButtonConverter : MvxValueConverter<bool, UIColor>
{
UIColor selectedColour = UIColor.FromRGB(128, 128, 128);
UIColor unSelectedColour = UIColor.GroupTableViewBackgroundColor;
protected override UIColor Convert(bool value, Type targetType, object parameter, CultureInfo culture)
{
return value ? selectedColour : unSelectedColour;
}
protected override bool ConvertBack(UIColor value, Type targetType, object parameter, CultureInfo culture)
{
return value == selectedColour;
}
}
Right, so what happens is, if i click the button in the cell, it runs the command that flips the bool value Selected, which in turn binds back to the background colour of the cell via the ButtonConverter value converter.
The problem I'm having is that the cell doesn't update straight away - only when I scroll out of view of that cell and back into view (ie the cell is redrawn). So i thought I'd just cause the cell to become 'dirty':
this.SelectButton.TouchUpInside += (o, e) =>
{
this.SetNeedsDisplay();
};
But this doesn't work. What does work is putting additional code inside the TouchUpInside event that manually changes the background colour. But I'm assuming this isn't the correct way of doing it.
Do I need to trigger RaisePropertyChanged when I change the value of Selected in the Expense object? How can I do that when it's just an object?
Really hoping Stuart can help out on this one ;)
I think your analysis is correct - the UI isn't updating live because there are no change messages from your Expense objects.
To provide 'traditional' change notifications in your view model objects, you need to make sure each one supports INotifyPropertyChanged. This small interface is easy to implement yourself if you want to - or you can modify your Expense to inherit the built-in MvxNotifyPropertyChanged helper class if you prefer - then RaisePropertyChanged would be available.
As one other alternative, you can also implement the new 'Rio' field based binding if you prefer. For an intro to this, see N=36 in http://mvvmcross.blogspot.com
I know Task has a method updateProgress, I would need to bind progressbar to task, however I cannot do that, as I do not have progressbar as an object.
My program has a TableView. Once user enters download url and clicks download new row created in the TableView. Row has some info and progressbar column. I then start a new thread - task. Where all download is being done and I need to update progress bar in that row somehow.
I tried binding SimpleDoubleProperty to the Task but it does not update progress bar...
James D solved this in Oracle JavaFX forum thread: Table cell progress indicator. I have just copied that solution into this answer.
The solution creates multiple tasks and monitors their progress via a set of progress bars in a TableView.
The original thread also includes a solution which uses ProgressIndicators in case you prefer those to ProgressBars.
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator ;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.ProgressBarTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ProgressBarTableCellTest extends Application {
#Override
public void start(Stage primaryStage) {
TableView<TestTask> table = new TableView<TestTask>();
Random rng = new Random();
for (int i = 0; i < 20; i++) {
table.getItems().add(
new TestTask(rng.nextInt(3000) + 2000, rng.nextInt(30) + 20));
}
TableColumn<TestTask, String> statusCol = new TableColumn("Status");
statusCol.setCellValueFactory(new PropertyValueFactory<TestTask, String>(
"message"));
statusCol.setPrefWidth(75);
TableColumn<TestTask, Double> progressCol = new TableColumn("Progress");
progressCol.setCellValueFactory(new PropertyValueFactory<TestTask, Double>(
"progress"));
progressCol
.setCellFactory(ProgressBarTableCell.<TestTask> forTableColumn());
table.getColumns().addAll(statusCol, progressCol);
BorderPane root = new BorderPane();
root.setCenter(table);
primaryStage.setScene(new Scene(root));
primaryStage.show();
ExecutorService executor = Executors.newFixedThreadPool(table.getItems().size(), new ThreadFactory() {
#Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
});
for (TestTask task : table.getItems()) {
executor.execute(task);
}
}
public static void main(String[] args) {
launch(args);
}
static class TestTask extends Task<Void> {
private final int waitTime; // milliseconds
private final int pauseTime; // milliseconds
public static final int NUM_ITERATIONS = 100;
TestTask(int waitTime, int pauseTime) {
this.waitTime = waitTime;
this.pauseTime = pauseTime;
}
#Override
protected Void call() throws Exception {
this.updateProgress(ProgressIndicator.INDETERMINATE_PROGRESS, 1);
this.updateMessage("Waiting...");
Thread.sleep(waitTime);
this.updateMessage("Running...");
for (int i = 0; i < NUM_ITERATIONS; i++) {
updateProgress((1.0 * i) / NUM_ITERATIONS, 1);
Thread.sleep(pauseTime);
}
this.updateMessage("Done");
this.updateProgress(1, 1);
return null;
}
}
}
Explanatory Text Based on Comment Questions
You only need to read this section if you are having difficulties understanding how the above code works and want to gain a deeper understanding of cell value and property connections.
There is no kind of binding here (at least I do not see).
The binding (or ChangeListener, which amounts to the same thing) is hidden behind the implementation of the PropertyValueFactory and the ProgressBarTableCell. Let's look at the relevant code:
TableColumn<TestTask, Double> progressCol = new TableColumn("Progress");
progressCol.setCellValueFactory(
new PropertyValueFactory<TestTask, Double>("progress")
);
progressCol.setCellFactory(
ProgressBarTableCell.<TestTask> forTableColumn()
);
The progressCol is defined to take a TestTask as the data row and extract a double value out of the test task property.
The cell value factory defines how the double value for the column is populated. It is defined based upon a PropertyValueFactory which takes the parameter "progress". This tells the property value factory to use JavaFX naming conventions and the Java reflection API to lookup relevant methods to retrieve the data from a TestTask instance. In this case it will invoke a method named progressProperty() on the TestTask instance to retrieve the ReadOnlyDoubleProperty reflecting the tasks progress.
As it states in it's documentation, the PropertyValueFactory is just short hand for the mess of code below, but the key fact is that it is returning an ObservableValue which the Table implementation can use to set the value of the cell as the cell changes.
TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<Person, String> p) {
// p.getValue() returns the Person instance for a particular TableView row
return p.getValue().firstNameProperty();
}
});
OK, so now we have a cell's value being reflected to the double value of the task's progress whenever the task makes any progress. But we still need to graphically represent that double value somehow. This is what the ProgressBarTableCell does. It is a table cell which contains a progress bar. The forTableColumn method creates a factory which produces the ProgressBarTableCells for each non-empty row in the column and sets the progress bar's progress to match the cell value which has been linked to the task's progress property by the PropertyValueFactory.
Confusing in understanding the detailed implementation . . . sure. But these high level helper factories and cells take care of a lot of the low level linkage details for you so that you don't need to code them over and over and from a plain API usage point of view it is (hopefully) simple and logical.
Also there is no properties (like SimpleStringProperty etc.) so the question would be, what if I need like two more columns with SimpleStringProperty, how do I add them to this kind of TableView?
Use the PropertyValueFactory once again. Let's image you have a string property called URL, then you can add the columns like this:
TableColumn<TestTask, Double> urlCol = new TableColumn("URL");
urlCol.setCellValueFactory(
new PropertyValueFactory<TestTask, Double>("url")
);
Note we only needed to set the cell value factory, this is because the default cell factory for the column will return a cell containing a label which directly displays the string value of the cell.
Now for the above to work correctly we need a method on TestTask which provides a url for the task, for example:
final ReadOnlyStringWrapper url = new ReadOnlyStringWrapper();
public TestTask(String url) {
this.url.set(url);
}
public ReadOnlyStringProperty urlProperty() {
return url.getReadOnlyProperty()
}
Note that the naming convention is really important here, it must be urlProperty() it can't be anything else or the PropertyValueFactory won't find the property value accessor.
Note for these purposes, a simple String value with a getUrl() would have worked just as well as a property as a PropertyValueFactory will work with a getter as well as a property method. The only advantage of using a property method is that it allows the table value data to update automatically based on property change events, which is not possible with a straight getter. But here because the url is effectively final and doesn't change for a given task, it doesn't make a difference whether a getter or property method is provided for this file from the task.
When accessing to outlets from my CustomClass : UICollectionViewCell, they are appearing as not initialized and can not set a proper value.
Every example I've seen it uses a plain Class (no XIB) to set the UI.
[Register("CustomCommentCell")]
public partial class CustomCommentCell : UICollectionViewCell
{
public static readonly NSString Identifier = new NSString("CustomCommentCell");
public CustomCommentCell () : base()
{
}
public CustomCommentCell (IntPtr handle) : base (handle)
{
}
public void updateData()
{
this.lblComment.Text = "Test";
}
}
On the other hand, I have registered the Class:
this.tableComments.RegisterClassForCell (typeof(CustomCommentCell),commentCellId);
and have the GetCell properly set.
However, when trying to set the outlets to a specific value, it indicates it is null. (this.lblcomment = null) while it should have been a UILabel initialized.
Any clues?
to create the Custom CollectionViewCell using XIB. do the following
1) create C# class which inherits from UIcollectionViewCell
[Register("MyCustomCell")]
public class MyCustomCell : UICollectionViewCell
{
public static readonly NSString Key = new NSString ("MyCustomCell");
[Export ("initWithFrame:")]
public MyCustomCell(CoreGraphics.CGRect frame) : base (frame)
{
}
public override UIView ContentView {
get {
var arr= NSBundle.MainBundle.LoadNib ("MyCustomCell", this, null);
UIView view =arr.GetItem<UIView> (0);
view.Frame = base.ContentView.Frame;
base.ContentView.AddSubview (view);
return base.ContentView;
}
}
}
2) Add a IphoneView XIB file has the Same Name as that of Class created in step 1
3) Open XIB in XCODE and do the Following Changes
3.1)Select the FileOwner set the Class same name as Step 1
3.2)Select The View Set the Class name UIView
4) Design Your XIB Accordingly
I can't follow quite the problem you are seeing. What is a "Custom XIB outlet"? Why is this question tagged "custom-controls"? Is there some example code or pictures you can show to help explain the problem?
The approach I use for UICollectionViewCell's is the same as I use for UITableViewCell - see the tutorial - http://slodge.blogspot.co.uk/2013/01/uitableviewcell-using-xib-editor.html
Update: From the code you've posted as a comment (not sure if it's complete or not), I think it would be useful for you to follow through that tutorial. There are a few steps to complete including registering the custom class name and including using RegisterNibForCellReuse - one of those will probably fix this for you.
I am using a custom UITableViewDelegate and in my controller I want to run some code when the tableview has a rowselected. I noticed the UITableViewDelegate already has an event called RowSelected but you cannot use it I'm guessing because there is a method in UITableViewDelegate with the exact same name.
If I write:
mytableviewdelegate.RowSelected += myeventhandler;
This will not compile and gives the error:
"Cannot assign to 'RowSelected' because it is a 'method group'"
Any ideas, I have a work around which is fine so Im really looking at working out if this is a bug in MonoTouch?
How are you implementing the custom UITableViewDelegate? I would suggest using Monotouch's UITableViewSource as it combines both the UITableViewDataSource and the UITableViewDelegate into one file which makes things so much easier.
Some example code:
(in your UIViewController that contains the UITableView)
tableView.Source = new CustomTableSource();
Then you'll want to create a new class for this:
public class CustomTableSource : UITableViewSource
{
public CustomTableSource()
{
// constructor
}
// Before you were assigning methods to the delegate/datasource using += but
// in here you'll want to do the following:
public override int RowsInSection (UITableView tableView, int section)
{
// you'll want to return the amount of rows you're expecting
return rowsInt;
}
// you will also need to override the GetCells method as a minimum.
// override any other methods you've used in the Delegate/Datasource
// the one you're looking for in particular is as follows:
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
// do what you need to here when a row is selected!
}
}
That should help you get started. In the UITableViewSource class you can always type public override and MonoDevelop will show you what methods are available to be overriden.