How to open custom dialog box / popup using Xamarin.Forms? - ipad

I am newbie to Xamarin.Forms and stuck with a situation where I want to open up a popup box with my control details [e.g. View Employee Details] on click of parent page.
How can I open custom dialog box / popup using Xamarin.Forms?
Any example code will be appreciated?
Thanks in advance!

If you still want to have your popup's code in its own Page you can set up some custom renderers along the following logic.
1. A ModalPage & corresponding renderer
public class ModalPage : ContentPage { }
public class ModalPageRenderer : PageRenderer {
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
this.View.BackgroundColor = UIColor.Clear;
this.ModalPresentationStyle = UIModalPresentationStyle.OverCurrentContext;
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
SetElementSize (new Size (View.Bounds.Width, View.Bounds.Height));
}
}
2. HostPage
public class ModalHostPage : ContentPage, IModalHost
{
#region IModalHost implementation
public Task DisplayPageModal(Page page)
{
var displayEvent = DisplayPageModalRequested;
Task completion = null;
if (displayEvent != null)
{
var eventArgs = new DisplayPageModalRequestedEventArgs(page);
displayEvent(this, eventArgs);
completion = eventArgs.DisplayingPageTask;
}
// If there is no task, just create a new completed one
return completion ?? Task.FromResult<object>(null);
}
#endregion
public event EventHandler<DisplayPageModalRequestedEventArgs> DisplayPageModalRequested;
public sealed class DisplayPageModalRequestedEventArgs : EventArgs
{
public Task DisplayingPageTask { get; set;}
public Page PageToDisplay { get; }
public DisplayPageModalRequestedEventArgs(Page modalPage)
{
PageToDisplay = modalPage;
}
}
}
3. HostPage renderer
public class ModalHostPageRenderer: PageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if(e.OldElement as ModalHostPage != null)
{
var hostPage = (ModalHostPage)e.OldElement;
hostPage.DisplayPageModalRequested -= OnDisplayPageModalRequested;
}
if (e.NewElement as ModalHostPage != null)
{
var hostPage = (ModalHostPage)e.NewElement;
hostPage.DisplayPageModalRequested += OnDisplayPageModalRequested;
}
}
void OnDisplayPageModalRequested(object sender, ModalHostPage.DisplayPageModalRequestedEventArgs e)
{
e.PageToDisplay.Parent = this.Element;
var renderer = RendererFactory.GetRenderer (e.PageToDisplay);
e.DisplayingPageTask = this.PresentViewControllerAsync(renderer.ViewController, true);
}
}
Then it is as simple as calling
await ModalHost.DisplayPageModal(new PopUpPage());
from your host page or in this particular case from the ViewModel behind.
What Pete said about PushModalAsync / PopModalAsync still remains valid for this solution too (which in my opinion is not a disadvantage), but your popup would appear with transparent background.
The main advantage of this approach, in my opinion, is that you can have your popup XAML/code definition separate from the host page and reuse it on any other page where you wish to show that popup.

The general purpose of what you are trying to achieve can be accomplished by using the PushModalAsync and PopModalAsync methods of Xamarin.Forms Navigation object.
The chances are that this is good enough for what you are needing - However - this isn't truely modal. I will explain after a small code snippet:-
StackLayout objStackLayout = new StackLayout()
{
};
//
Button cmdButton_LaunchModalPage = new Button();
cmdButton_LaunchModalPage.Text = "Launch Modal Window";
objStackLayout.Children.Add(cmdButton_LaunchModalPage);
//
cmdButton_LaunchModalPage.Clicked += (async (o2, e2) =>
{
ContentPage objModalPage = new ContentPage();
objModalPage.Content = await CreatePageContent_Page2();
//
await Navigation.PushModalAsync(objModalPage);
//
// Code will get executed immediately here before the page is dismissed above.
});
//
return objStackLayout;
private async Task<StackLayout> CreatePageContent_Page2()
{
StackLayout objStackLayout = new StackLayout()
{
};
//
Button cmdButton_CloseModalPage = new Button();
cmdButton_CloseModalPage.Text = "Close";
objStackLayout.Children.Add(cmdButton_CloseModalPage);
//
cmdButton_CloseModalPage.Clicked += ((o2, e2) =>
{
this.Navigation.PopModalAsync();
});
//
return objStackLayout;
}
The problem with the above is that the
await Navigation.PushModalAsync(objModalPage);
will immediately return after the animation.
Although you can't interact with the previous page, as we are displaying a new NavigationPage with a Close button shown - the parent Navigation Page is still executing behind the scenes in parallel.
So if you had any timers or anything executing these still would get called unless you stopped those.
You could also use the TaskCompletionSource approach as outlined in the following post also How can I await modal form dismissal using Xamarin.Forms?.
Note - that although you can now await the 2nd page displaying and then when that page is dismissed allowing code execution to continue on the next line - this is still not truely a modal form. Again timers or anything executing still will get called on the parent page.
Update 1:-
To have the content appear over the top of existing content then simply include it on the current page, however make this section of content invisible until you need it.
If you use an outer container such like a Grid that supports multiple child controls in the same cell, then you will be able to achieve what you want.
You will also want to use something like a filled Box with transparency that will cover the entire page also, to control the visible, see through section, that surrounds your inner content section.

I followed above approach and found it impossible to run on iOS 7.
I found this library BTProgressHUD which you can modify and use.
I Use its methods by Dependency service.
Actual library for popups.
https://github.com/nicwise/BTProgressHUD
Following example uses BTProgressHUD library internally.
https://github.com/xximjasonxx/ScorePredictForms

Related

Expanding a component in a PopupView - Vaadin

I am using Vaadin 8.5.1 with the Vaading Desiin combination with Spring Boot 2.0.4.
Currently I am trying to add a PopupView at the bottom of the page which opens on button click. In the Popup there is a vertical layout including two components: a HorizontalSplitPanel and a Button. The PopupView should have the width of the current BrowserWindow and one third of the height.
The HorizontalSplitPanel should use all the space in the popup, which is not needed for the button.
What I did:
#SpringComponent
#UIScope
public class View extends VerticalLayout implements Observer {
private final PopupContentView popupContentView;
private PopupView popup;
#Autowired
public View(PopupContentView popupContentView) {
this.popupContentView = popupContentView;
}
#PostConstruct
void init() {
button.addClickListener(clickEvent -> openPopup());
}
private void openPopup() {
if (popup == null) {
setSizeOfPopUp();
// popup will adjust automatically to size of content
popup = new PopupView(null, popupContentView);
popup.addPopupVisibilityListener(event -> {
if (event.isPopupVisible()) {
popupContentView.build(this::submitted);
}
});
popup.setHideOnMouseOut(false);
this.addComponent(popup);
}
popup.setPopupVisible(true);
}
private void setSizeOfPopUp() {
if (popupContentView != null) {
popupContentView.setWidth(Page.getCurrent().getBrowserWindowWidth(), Unit.PIXELS);
popupContentView.setHeight(((float) Page.getCurrent().getBrowserWindowHeight()) / 3, Unit.PIXELS);
}
}
private void submitted() {
// do some stuff
}
#Override
public void update(Observable observable, Object o) {
if (observable instanceof BrowserWindowResizeListenerObservable) {
setSizeOfPopUp();
}
}
}
#Service
public class BrowserWindowResizeListenerObservable extends Observable implements Page.BrowserWindowResizeListener {
#Override
public void browserWindowResized(Page.BrowserWindowResizeEvent browserWindowResizeEvent) {
this.setChanged();
this.notifyObservers();
}
}
#SpringComponent
#UIScope
public class PopupContentView extends VerticalLayout {
private SubmitCallback submitCallback;
private Button submitBtn;
#PostConstruct
void init() {
super.init();
}
void build(#NotNull SubmitCallback) {
removeAllComponents();
this.addComponent(horizontalSplitPanel);
this.addComponent(submitBtn);
this.setExpandRatio(horizontalSplitPanel, 1.0f);
this.submitCallback = callback;
}
private void submit() {
submitCallback.submit(someContent);
}
#FunctionalInterface
public interface SubmitCallback {
void submit(SomeContent someContent);
}
}
As you can see, I have a main view, a view for the content and a listener class.
What I want to happen is that the popup is visible on button click and contains the content view with the panel and the submit button. The panel takes the rest of the space, which is not needed for the button. and the popup is fully filled with content.
What actually happens is that the panel takes the full space of the popup and the button will be shown below the popup.
However, when I resize the window and the resizing event gets fired, everything is fine and the button is no longer below the popup.
It seems to be that the padding and the margin (which are the HTML implementation of the expand ratio in Vaadin) are calculated at an earlier stage and get triggered again when resizing the window. However, I have no clue when and what I need to do, to trigger it.
Does anyone have an idea, how can fix this?
EDIT:
When I have a Tree component or a DateField component in the PopupView and then expand a tree element or change the value of the DateField by selecting a value from the Date popup, the resizing is done correctly and everything is fine.
I think in your case the method of checking Browser window size and calculating target pixel size is too complex for your case. I would recommend just to set the width of the popup to be 100% and height to be 33%, like component.setHeight("33%"), yes you can use percentages for width and height instead of pixels. Then sizing is done by CSS, and it will react faster to browser window sizing without server round trip.

ICommand not always firing when tab selected

I have a simple ActionBar with 3 tabs attached. When a tab is clicked, the fragment is inflated and the view shows. The tab being click event is fired using an event. Initially, the first fragment is inflated, but the others respond and inflate if clicked.
If I change the event being fired to an ICommand, only the last fragment is inflated and then if I click on the first tab, that and the last are inflated. Never the second.
My code is this
ICommand TabClicked
{
get
{
return new RelayCommand(() =>
{
tab.TabSelected += (object sender, ActionBar.TabEventArgs e) => TabOnTabSelected(sender, e);
});
}
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
fragments.Add(new TODFragment());
fragments.Add(new ConditionsFragment());
fragments.Add(new ResultsFragment());
AddTabToActionBar("Time", Resource.Drawable.crucifix_colour);
AddTabToActionBar("Conditions", Resource.Drawable.weather_colour);
AddTabToActionBar("Results", Resource.Drawable.tod_colour);
}
void AddTabToActionBar(string text, int iconResourceId)
{
tab = ActionBar.NewTab().SetTag(text).SetText(text).SetIcon(iconResourceId);
/* uncomment and comment out one of the two below to see the difference in operation */
tab.TabSelected += TabOnTabSelected;
//tab.SetCommand<ActionBar.TabEventArgs>("TabSelected", TabClicked);
ActionBar.AddTab(tab);
}
void TabOnTabSelected(object sender, ActionBar.TabEventArgs tabEventArgs)
{
var tabNo = sender as ActionBar.Tab;
var frag = fragments[tabNo.Position];
tabEventArgs.FragmentTransaction.Replace(Resource.Id.frameLayout1, frag);
}
Am I missing something fundamental here in the difference between ICommands and Events or is it something else?
I'm using Xam.Android and MVVMLight
I found the answer. When I create the partial class I define the UI objects like this (or something like this at least)
EditText myEditText;
EditText MyEditText = myEditText ?? (view.FindViewById<EditText>(Resources.Id.myEdit);
This is fine, but it does mean that once defined, it doesn't get redefined.
Not a problem if the UI is not really going to change, but every time an action tab is pressed, the fragment is refreshed. Only problem is the Id isn't changing as myEditText is not null.
The answer is add a method in the UI definition code that nulls the objects then in the main code, when the UI disappears, call the nulling method. Everything works then

VaadinSession attribute and updating session-bound components

I have a Vaadin Navigator with multiple View elements. Each view has a different purpose however some also contain common traits that I have put inside custom components.
One of those custom components is the menu - it is positioned at the top and allows navigation between the different views. I create and add this component inside the constructor of each view (if you are interested in the menu's implementation see the end of this post). Here is a skeleton for each custom view:
class MyViewX implements View {
MenuViewComponent mvc;
public MyViewX() {
mvc = new MenuViewComponent();
addComponent(mvc);
}
#Override
public void enter(ViewChangeEvent event) {
}
}
So far, so good. In order to make things simple I will explain my problem using a simple label and not one of my other custom components but the dependency that I will describe here is the same for those components just like with the label.
Let's say I have a label which sole purpose is to display a greeting with the user's username. In order to do that I use VaadinSession where I store the attribute. This is done by my LoginController, which validates the user by looking into a database and if the user is present, the attribute is set and one of the views is opened automatically. The problem is that VaadinSession.getCurrent().getAttribute("username") returns null when called inside the constructor. This of course makes sense omho because a constructor should not be bound by a session-attribute.
So far I have managed to use the enter() method where there is no problem in retrieving session attributes:
class MyViewX implements View {
MenuViewComponent mvc;
public MyViewX() {
mvc = new MenuViewComponent();
addComponent(mvc);
}
#Override
public void enter(ViewChangeEvent event) {
String username = (String)VaadinSession.getCurrent().getAttribute("username");
Label greeting = new Label("Hello " + username);
addComponent(greeting);
}
}
The issue that comes from this is obvious - whenever I open the view where this label is present, a new label is added so if I re-visit the view 10 times, I will get 10 labels. Even if I move the label to be a class member variable the addComponent(...) is the one that screws things up. Some of my custom components really depend on the username attribute (in order to display user-specific content) hence I also have to place those in the enter(...) method. The addComponent(...) makes a mess out of it. I even tried the dirty way of removing a component and then re-adding it alas! in vain:
class MyViewX implements View {
MenuViewComponent mvc;
Label greeting;
public MyViewX() {
mvc = new MenuViewComponent();
addComponent(mvc);
}
#Override
public void enter(ViewChangeEvent event) {
String username = (String)VaadinSession.getCurrent().getAttribute("username");
greeting = new Label("Hello " + username);
// Remove if present
try { removeComponent(greeting); }
catch(Exception ex) { }
// Add again but with new content
addComponent(greeting);
}
}
but it's still not working. So my question is: what is the simplest way of updating a component that requires session-bound attributes?
The navigation via the menu custom component is omho not the issue here since all components of the menu are loaded in it's constructor. That's why it's also load that component in particular in a view's own constructor. Here is an example of a button in my menu that opens a view:
#SuppressWarnings("serial")
#PreserveOnRefresh
public class MenuViewComponent extends CustomComponent {
public MenuViewComponent(boolean adminMode) {
HorizontalLayout layout = new HorizontalLayout();
Label title = new Label("<h2><b>Vaadin Research Project</b></h2>");
title.setContentMode(ContentMode.HTML);
layout.addComponent(title);
layout.setComponentAlignment(title, Alignment.TOP_LEFT);
Button personalDashboardButton = new Button("Personal dashboard", new Button.ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
getUI().getNavigator().navigateTo(MainController.PERSONALDASHBOARDVIEW);
}
});
personalDashboardButton.setStyleName(BaseTheme.BUTTON_LINK);
layout.addComponent(personalDashboardButton);
layout.setComponentAlignment(personalDashboardButton, Alignment.TOP_CENTER);
// Add other buttons for other views
layout.setSizeUndefined();
layout.setSpacing(true);
setSizeUndefined();
setCompositionRoot(layout);
}
}
PERSONALDASHBOARDVIEW is just one of the many views I have.
It may be worth considering how long should your view instances "live", just as long they're displayed, until the session ends or a mix of the two. With this in mind and depending on what needs to happen when you enter/re-enter a view, you have at least the following 3 options:
1) Recreate the whole view (allowing for early view garbage-collection)
first register a ClassBasedViewProvider (instead of a StaticViewProvider) which does not hold references to the created views:
navigator = new Navigator(this, viewDisplay);
navigator.addProvider(new Navigator.ClassBasedViewProvider(MyView.NAME, MyView.class));
simple view implementation
public class MyView extends VerticalLayout implements View {
public static final String NAME = "myViewName";
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// initialize tables, charts and all the other cool stuff
addComponent(new SweetComponentWithLotsOfStuff());
}
}
2) Keep some already created components and replace others
public class MyView extends VerticalLayout implements View {
private MySweetComponentWithLotsOfStuff mySweetComponentWithLotsOfStuff;
public MyView() {
// initialize only critical stuff here or things that don't change on enter
addComponent(new MyNavigationBar());
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// oh, so the user does indeed want to see stuff. great, let's do some cleanup first
removeComponent(mySweetComponentWithLotsOfStuff);
// initialize tables, charts and all the other cool stuff
mySweetComponentWithLotsOfStuff = new SweetComponentWithLotsOfStuff();
// show it
addComponent(mySweetComponentWithLotsOfStuff);
}
}
3) Lazy creating and updating (or not) the content when entering
public class MyView extends VerticalLayout implements View {
private boolean isFirstDisplay = true;
private MySweetComponentWithLotsOfStuff mySweetComponentWithLotsOfStuff;
public MyView() {
// initialize only critical stuff here, as the user may not even see this view
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// oh, so the user does indeed want to see stuff
if (isFirstDisplay) {
isFirstDisplay = false;
// lazily initialize tables, charts and all the other cool stuff
mySweetComponentWithLotsOfStuff = new SweetComponentWithLotsOfStuff();
addComponent(mySweetComponentWithLotsOfStuff);
} else {
// maybe trigger component updates, or simply don't do anything
mySweetComponentWithLotsOfStuff.updateWhateverIsRequired();
}
}
}
I'm sure (and curious) that there may be other options, but I've mainly used a variation of 1) using spring with prototype views and component tabs.

MvvmCross iOS: how to dismiss a view controller when use IMvxModelTouchView

I have used IMvXModelTouchView for a custom popup screen animation. And, I have a close button on this popup view. What is the proper way to switch back to a previous view?
Here is my code look like:
public class PopupView
: MvxViewController, IMvxModalTouchView
{
public PopupView()
{
ModalPresentationStyle = UIModalPresentationStyle.PageSheet;
}
public override void ViewDidLoad()
{
Title = "Map";
base.ViewDidLoad();
var closeButton = new UIButton(new RectangleF(0, 0, 50, 30));
closeButton.TouchUpInside += CloseButtonClicked();
Add(closeButton);
}
private EventHandler CloseButtonClicked()
{
return (sender, args) => NavigationController.DismissViewController(true, null);
}
}
It worked, the first time when I click this close button, but it crash when I tried to pop up this view again.
I suspect you are probably using the standard MvxModalSupportTouchViewPresenter which only expects one modal view/viewModel to be displayed at a time, and which expects that view/viewModel to be cleared using Close(this) from the ViewModel.
See: MvxModalSupportTouchViewPresenter.cs#L29
If you have strong ideas about your UI, then (in my opinion) your best bet is to write your own custom presenter - then you can open/close/hide/show whatever you want. For more on writing custom presenters, see some of the links and videos from: http://slodge.blogspot.com/2013/06/presenter-roundup.html

Monotouch.Dialog - How to push a view from an Element

It seems like this should be very easy, but I'm missing something. I have a custom Element:
public class PostSummaryElement:StyledMultilineElement,IElementSizing
When the element's accessory is clicked on, I want to push a view onto the stack. I.e. something like this:
this.AccessoryTapped += () => {
Console.WriteLine ("Tapped");
if (MyParent != null) {
MyParent.PresentViewController(new MyDemoController("Details"),false,null);
}
};
Where MyDemoController's gui is created with monotouch.dialog.
I'm just trying to break up the gui into Views and Controlls, where a control can push a view onto the stack, wiat for something to happen, and then the user navigates back to the previous view wich contains the control.
Any thought?
Thanks.
I'd recommend you not to hardcode behavior in AccessoryTapped method, because the day when you'll want to use that component in another place of your project is very close. And probably in nearest future you'll need some another behavior or for example it will be another project without MyDemoController at all.
So I propose you to create the following property:
public Action accessoryTapped;
in your element and its view, and then modify your AccessoryTapped is that way:
this.AccessoryTapped += () => {
Console.WriteLine ("Tapped");
if (accessoryTapped != null) {
accessoryTapped();
}
};
So you'll need to create PostSummaryElement objects in following way:
var myElement = new PostSummaryElement() {
accessoryTapped = someFunction,
}
...
void someFunction()
{
NavigationController.PushViewController (new MyDemoController("Details"), true);
}

Resources