I am trying to learn how to use the UISplitViewController and I am using the "SplitView" example available from Monotouch. I understand how it works and communicates between the Master and Detail controllers.
Therefore it can't be hard to change the detail controller? So I have made a very small amendment and it doesn't work! I have read you must use the SetViewControllers but this doesn't exist in the Monotouch stack. Where am I going wrong?
It's exactly the same code as the example from Montouch except for the lines below:
public class MainSplitView : UISplitViewController
{
protected Screens.MasterView.MasterTableView masterView;
protected Screens.DetailView.DetailViewScreen detailView;
protected TestViewController testViewController; // *** Added this line
public MainSplitView () : base()
{
// create our master and detail views
masterView = new Screens.MasterView.MasterTableView ();
detailView = new Screens.DetailView.DetailViewScreen ();
masterView.RowClicked += (object sender, MasterView.MasterTableView.RowClickedEventArgs e) =>
{
detailView.Text = e.Item;
testViewController = new TestViewController(); // *** Added this line
ViewControllers[0] = masterView; // *** Added this line
ViewControllers[1] = testViewController; // *** Added this line
// the UISplitViewController.SetViewControllers does not exist! ???
};
ViewControllers = new UIViewController[] { masterView, detailView };
}
All help greatly appreciated!
Mike
Set ViewControllers exactly how do you do are doing it in the other place:
ViewControllers = new UIViewController[] { masterView, detailView };
Just do this:
ViewControllers = new UIViewController[] { masterView, testViewController };
Related
my web application offers a download. Javascript creats at the click the url (it depends on the user input) and the browser should open it, so that the page isn't reloaded.
For that, I think I have to alternatives:
// Alt1:
window.open(pathToFile);
// Alt2:
var downloadFrame = document.getElementById('downloads');
if (downloadFrame === null) {
downloadFrame = document.createElement('iframe');
downloadFrame.id = 'downloads';
downloadFrame.style.display = 'none';
document.body.appendChild(downloadFrame);
}
downloadFrame.src = pathToFile;
Both works under Firefox. Problem with open new window method: If the creation of the file at the server needs more time, the new empty tab will be closed late.
Problem with iframe: If there is an error at the server, no feedback is given.
I think at firefox the iframe is the better solution. But the web application must run with an JavaFX WebView, too. JavaFX haven't a download feature, I have to write it. One possible way is to listen on the location property:
final WebView webView = new WebView();
webView.getEngine().locationProperty().addListener(new ChangeListener<String>() {
#Override public void changed(ObservableValue<? extends String> observableValue, String oldLoc, String newLoc) {
if (newLoc.cotains("/download")) {
FileChooser chooser = new FileChooser();
chooser.setTitle("Save " + newLoc);
File saveFile = chooser.showSaveDialog(webView.getEngine().getScene().getWindow());
if (saveFile != null) {
BufferedInputStream is = null;
BufferedOutputStream os = null;
try {
is = new BufferedInputStream(new URL(newLoc).openStream());
os = new BufferedOutputStream(new FileOutputStream(saveFile));
while ((readBytes = is.read()) != -1) {
os.write(b);
}
} finally {
try { if (is != null) is.close(); } catch (IOException e) {}
try { if (os != null) os.close(); } catch (IOException e) {}
}
}
}
}
}
There are some problems:
The download start depends on a part of the url, because JafaFX supports no access to the http headers (that is bearable)
If the user starts the download with the same url two times, only the first download works (the change event only fires, if the url is new). I can crate unique urls (with #1, #2 and so on at the end). But this is ugly.
Only the "window.open(pathToFile);" method works. Loading an iframe don't fire the change location event of the website. That is expectable but I haven't found the right Listener.
Can someone help me to solve 2. or 3.?
Thank you!
PS: Sorry for my bad english.
edit:
For 2. I found a way. I don't know if it is a good one, if it is performant, if the new webview is deleted or is in the cache after download, ....
And the user don't get an feedback, when some a problem is raised:
webView.getEngine().setCreatePopupHandler(new Callback<PopupFeatures, WebEngine>() {
#Override public WebEngine call(PopupFeatures config) {
final WebView downloader = new WebView();
downloader.getEngine().locationProperty().addListener(/* The Listener from above */);
return downloader.getEngine();
}
}
I think you may just need to use copyURLtoFile to get the file...call that when the location changes or just call that using a registered java class. Something like this:
org.apache.commons.io.FileUtils.copyURLToFile(new URL(newLoc), new File(System.getProperty("user.home")+filename));
Using copyURLToFile the current page doesn't have to serve the file. I think registering the class is probably the easiest way to go... something like this:
PHP Code:
Download $filename
Java (in-line class in your javafx class/window... in this case my javafx window is inside of a jframe):
public class JavaApp {
JFrame cloudFrameREF;
JavaApp(JFrame cloudFrameREF)
{
this.cloudFrameREF = cloudFrameREF;
}
public void getfile(String filename) {
String newLoc = "http://your_web_site.com/send_file.php?filename=" + filename;
org.apache.commons.io.FileUtils.copyURLToFile(new URL(newLoc), new File(System.getProperty("user.home")+filename));
}
}
This part would go in the main javafx class:
Platform.runLater(new Runnable() {
#Override
public void run() {
browser2 = new WebView();
webEngine = browser2.getEngine();
appREF = new JavaApp(cloudFrame);
webEngine.getLoadWorker().stateProperty().addListener(
new ChangeListener<State>() {
#Override public void changed(ObservableValue ov, State oldState, State newState) {
if (newState == Worker.State.SUCCEEDED) {
JSObject win
= (JSObject) webEngine.executeScript("window");
// this next line registers the JavaApp class with the page... you can then call it from javascript using "app.method_name".
win.setMember("app", appREF);
}
}
});
You may not need the frame reference... I was hacking some of my own code to test this out and the ref was useful for other things...
EDIT 2: If you're looking for an answer to a similar problem, check Stuart's answer and my comments on it.
EDIT: I am actually getting a Mono.Debugger.Soft.VMDisconnectedException. I also recently installed Windows 8.1 and Resharper (though Resharper is suspended now).
When I access a very simple list property of my view model in my MVVMCross Xamarin iOS application, the program fails. It doesn't quit most of the time: it acts like it's running. The simulator has a black screen and there is no exception. If I breakpoint on if (messagesViewModel != null) source.ItemsSource = messagesViewModel.Messages; and then type messagesViewModel.Messages into the Immediate Window, everything stops, so I can tell it is failing at this line. If instead I "step over", it never moves to the next line.
I was having similar behavior when I was toggling this code in the MvxTableViewSource:
public override int RowsInSection(UITableView tableview, int section)
{
return 1;
}
My view model looks like this:
public class MessagesViewModel : MvxViewModel
{
private List<BaseMessage> _messages = null;
public List<BaseMessage> Messages
{
get
{
return _messages; //yes, I know I'm returning null
//I wasn't at first.
}
}
public MessagesViewModel()
{
}
}
This is my ViewDIdLoad on the MvxTableViewController:
public override void ViewDidLoad()
{
base.ViewDidLoad();
var source = new MessagesTableViewSource(TableView);
//was binding here, removed it for debug purposes
//failure on second line here
var messagesViewModel = ViewModel as MessagesViewModel;
if (messagesViewModel != null) source.ItemsSource = messagesViewModel.Messages;
TableView.Source = source;
TableView.ReloadData();
}
Some initialization code:
public class App : MvxApplication
{
public App()
{
var appStart = new MvxAppStart<MessagesViewModel>();
Mvx.RegisterSingleton<IMvxAppStart>(appStart);
}
}
public partial class AppDelegate : MvxApplicationDelegate
{
//empty functions removed.
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Window = new UIWindow(UIScreen.MainScreen.Bounds);
var presenter = new MvxTouchViewPresenter(this, Window);
var setup = new Setup(this, presenter);
setup.Initialize();
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
Window.MakeKeyAndVisible();
return true;
}
}
I suspect whatever the error is, it isn't in any of the code you have posted.
I just created a simple ViewModel:
public class FirstViewModel
: MvxViewModel
{
private List<string> _items = new List<string>() { "One", "Two", "Three"};
public List<string> Items
{
get { return _items; }
set { _items = value; RaisePropertyChanged(() => Items); }
}
}
And a simple View:
[Register("FirstView")]
public class FirstView : MvxTableViewController
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
// ios7 layout
if (RespondsToSelector(new Selector("edgesForExtendedLayout")))
EdgesForExtendedLayout = UIRectEdge.None;
var firstViewModel = ViewModel as FirstViewModel;
var source = new MessagesTableViewSource(TableView);
source.ItemsSource = firstViewModel.Items;
TableView.Source = source;
}
public class MessagesTableViewSource : MvxTableViewSource
{
public MessagesTableViewSource(UITableView tableView) : base(tableView)
{
tableView.RegisterClassForCellReuse(typeof(MessagesCell), new NSString("MessagesCell"));
}
protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
{
return tableView.DequeueReusableCell("MessagesCell");
}
}
public class MessagesCell : MvxTableViewCell
{
public MessagesCell(IntPtr handle)
: base(handle)
{
var txt = new UILabel(new RectangleF(0, 0, 320, 44));
Add(txt);
this.DelayBind(() =>
{
this.CreateBinding(txt).Apply();
});
}
}
}
And this code runs fine...
I wouldn't completely trust the integration of Xamarin.iOS with the Immediate window - it is better now than it used to be, but I've seen several problems with it before.
Some things to possibly check:
does the above code work for you?
if it does, then what's in your BaseMessage and MessagesTableViewSource classes - perhaps they are causing the problem?
can you use Mvx.Trace("The list is {0}", messagesViewModel.Messages ?? "-null") to view the list? Can you use trace within the ViewModel property get - is it being called? Can you use trace within the ViewModel constructor?
are all your assemblies building against the same versions of things? Are all your assemblies definitely rebuilt? (Check "Build|Configuration Manager")- what version of Xamarin.iOS are you running in VS and in the Mac?
I want to use a DialogViewController inside of a UITabViewController.
Problem: Nested elements don't show a navigation bar, and so it is not possible to go back.
When I push my class (inherited from DialogViewController) to a UINavigationController, then the behavior is correct. If I use the same class in a tab of a UITabViewController (even with an underlying UINavigationController), then the behaviour is wrong.
Can anyone help me out?
Although the question is not assisted with some code sample, I made a small example hoping to solve your question. For this example I used the Tabbed Application template which comes with Xamarin.iOS and named it TabbingTest.
The following code goes in the AppDelegate. Change the FinishedLaunching method to contain:
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
window = new UIWindow (UIScreen.MainScreen.Bounds);
var viewControllers = new UIViewController[]
{
CreateTabFor("Test", "first", new TestDialogController ()),
CreateTabFor("Second", "second", new SecondViewController ()),
};
tabBarController = new UITabBarController ();
tabBarController.ViewControllers = viewControllers;
tabBarController.SelectedViewController = tabBarController.ViewControllers[0];
window.RootViewController = tabBarController;
window.MakeKeyAndVisible ();
return true;
}
Then add the following methods:
private int _createdSoFarCount = 0;
private UIViewController CreateTabFor(string title, string imageName, UIViewController view)
{
var controller = new UINavigationController();
controller.NavigationBar.TintColor = UIColor.Black;
var screen = view;
SetTitleAndTabBarItem(screen, title, imageName);
controller.PushViewController(screen, false);
return controller;
}
private void SetTitleAndTabBarItem(UIViewController screen, string title, string imageName)
{
screen.Title = NSBundle.MainBundle.LocalizedString (title, title);
screen.TabBarItem = new UITabBarItem(title, UIImage.FromBundle(imageName),
_createdSoFarCount);
_createdSoFarCount++;
}
Create a class named TestDialogController and paste the following code inside.
using System;
using MonoTouch.Dialog;
using MonoTouch.UIKit;
namespace TabbingTest
{
public class TestDialogController : DialogViewController
{
public TestDialogController (): base(UITableViewStyle.Plain,null,false)
{
var root = new RootElement ("Tabbing test"){
new Section (){
new RootElement ("First level", 0, 0) {
new Section (null, "This is the first level."){
new RootElement ("Second level", 0, 0) {
new Section (null, "This is the second level."){
new BooleanElement ("Flipflops", false)
}
}
}
}}
};
this.Root = root;
}
}
}
Now run the application.
You can see that even the nested elements show up nicely in the navigation bar. Even with multilevel nesting.
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.
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.