Handling secondary popup controller events from main controller JavaFX - events

I have a main controller which handles my main.fxml and a second controller which handles my popup.fxml
When a button is pressed from the main controller, the popup windows appears. In the popup window you add players. The players are added by textfield to an array and must be sent back to main controller. I have a button called "btnApply" in my popup controller, when that is pressed I want to close the popup window and handle the array from my main controller class. I only want my main controller class to be aware of the popup.
This is how I am creating a popup from main controller:
button.setOnAction(e -> newWindow());
public void newWindow(){
try{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("popup.fxml"));
Parent popupRoot = fxmlLoader.load();
Stage playerStage = new Stage();
playerStage.setTitle("Player");
playerStage.setScene(new Scene(popupRoot, 720, 600));
playerStage.show();
}catch(Exception e) {
e.printStackTrace();
}
}
Now the question is how to I get the event or the object. When I created the popup window without using FXML (created the GUI manually), it was easy because I just made an object of the Class Popup and had a getButton() and getArray(). In my main controller class I had a Popup popup = new Popup(); then I had a method where I handle the button from my popup class popup.getButton().setOnAction(e -> addPlayers());
But this is not possible using fxml. I cant seem to get the object that is running. If I were to create a Popup popup I will just get a new event not the one that is being ran.

The way to do this most similar to your previous approach would be adding the getButton() method to to your controller class and get the controller class from the FXMLLoader:
Parent popupRoot = fxmlLoader.load();
MyController controller = fxmlLoader.<MyController>getController();
controller.getButton()...
Alternatives
However I recommend a different approach to passing the data, since this way you limit yourself to a single button in the popup as the only way to submit the players. I'd rather do this by "notifying" the class creating the popup, i.e:
Implement this interface in the calling class
public interface PlayerContainer {
void addPlayers(Player[] players);
}
and add this to the controller:
private PlayerContainer playerContainer;
public void setPlayerContainer(PlayerContainer playerContainer) {
this.playerContainer = playerContainer;
}
And pass the calling class to the controller directly after loading the popup content:
Parent popupRoot = fxmlLoader.load();
MyController controller = fxmlLoader.<MyController>getController();
controller.setPlayerContainer(this);
and when the user submits the player data, simply call
this.playerContainer.addPlayers(playerData);
in addition to closing the window. Passing a ObservableList<Player> to the controller class instead and adding all players to this list would work too, if you handle changes to the list appropriately in the calling class.
Take a look at jewelsea's answer to "Passing Parameters JavaFX FXML". This lists some alternative ways to can pass objects to the controller of the fxml. The Setting a Controller on the FXMLLoader approach could be easy to implement, e.g. if you use a inner class of the calling class as the controller class. This way it's harder to reuse the popup than with the approach described above however...

Related

Crash for second view object's name assign

I am new to Xamarin and MVVMCross. So I have created 2 views. Login and Register. I have a button on Login to goto Register view and I am going there by this code in the Login's ViewModel:
// method when user tap register button
public IMvxCommand NavigateRegister
{
get { return new MvxCommand(() => ShowViewModel<RegisterViewModel>()); }
}
It works ok the Register Page opens well. But once I assign Name for a single object on Register view (a textEdit), the app crash when I tap on the Register button.
Below is the error msg:
Xamarin.iOS: Received unhandled ObjectiveC exception:
NSUnknownKeyException [
setValue:forUndefinedKey:]: this class is not key value
coding-compliant for the key regNameEdit.
EDIT:
More details: I already assigned the name (see pic below), but still crash:
And the view also been assigned to its Class "CreateAccount". But I am noticing the class declaration has "partial" darkened out in the "public partial class CreateAccount : MvxViewController" line. That's the only noticeable difference btw this class and the first one.
using MvvmCross.Binding.BindingContext;
using MvvmCross.iOS.Views;
namespace MyApp.iOS.Views
{
public partial class CreateAccount : MvxViewController
{
public CreateAccount() : base("CreateAccount", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
Title = "Register";
var set = this.CreateBindingSet<CreateAccount, Core.ViewModels.CreateAccountModel>();
set.Bind(regNameEdit).To(vm => vm.NameStr);
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
}
}
The Bind(regNameEdit) also is an error (not detecting the textedit still)
This usually means that the a control is not defined in the View/ViewController class in your case the regNameEdit.
Make sure you created the back Property for this Edit and that the class assigned to the XIB is the one containing this property.
If you are using Xamarin Studio Designer you create the back property selecting the UIControl in the XIB/StoryBoard and setting a name, then enter.
This will create a property with the name you specified accessible in the ViewController.
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.MyUITextField.Text = string.Empty;
}
UPDATE
When using Storyboard:
Try this: Remove the name from the TextField and also remove the name from the ViewController class, then clean your project and rebuild. Re-add the class to the ViewController but when doing it click over the yellow icon in the bottom, there put the name and hit enter. Continue with the TextField, select it put the name and hit enter.
UPDATE # 2
When using XIBs
When you create the ViewController from the menu, Xamarin Studio will create both the ViewController class and the XIB file and will associate one with the other so here you don't have to do anything else to link them.
For the TextField you will need to do it adding the name as previously indicated.
Try this: Remove the name of the UITextField and save and clean/rebuild the project then add the name and hit enter.
Something you can do to verify if there's any problem, double click on the button in the XIB and this should take you to the ViewController class to create a method.
Hope this helps.

Xamarin forms Navigation issue. Popped page stays active

I have a page "PageA" which listens to a Changed event on a global object.
The page is added in the navigation stack like this:
await Navigation.PushAsync(new PageA());
The page is removed by clicking the back button in the navigation bar.
When I do the same x times, I have x times PageA instances listening to the Changed event. So PageA is created over and over again, but is never really removed.
How do I really get rid of the PageA when clicking the back button?
Example of what you are doing
public partial class MyPage : ContentPage
{
MyPage()
{
this.SomeEvent += SomeEventBeyondTheScopeOfThisPage();
}
protected override void OnDisappearing()
{
//You should add this
this.SomeEvent -= SomeEventBeyondTheScopeOfThisPage();
}
}
Which even if the page is popped from your navigation stack does not remove the reference from MyPage.SomeEvent to SomeEventBeyondTheScopeOfThisPage. So when the Garbage Collector comes along this page is going to stay and this event is going to continue to be listened for.
Just detach the event in OnDisappearing for the simple answer. You dont dispose the page you just detach all references and events outside of its scope. A better idea would be to make your code more modular and not worry about detaching the event. If the event source were coming from within the page itself it would not need to be detached.
public partial class MyPage : ContentPage
{
MyPage()
{
this.SomeEvent += SomeEventWithinPagesScope();
}
private Task SomeEventWithinPagesScope()
{
//My cool event goes here
}
}
If it does have to be global another option would be Messaging Center. I do not believe you have to detach those listeners but I could be wrong.
https://developer.xamarin.com/guides/xamarin-forms/messaging-center/
another reason: NavigationPage.CurrentNavigationTask.Result still refer to the popped page instance. that is why GC will not collect it. unless you navigate to other pages.

Update the view although the model hasn't changed

My app updates the view in response to events dispatched by the model. But what if the model hasn't changed, but I still need to update the view. For example, I've closed and reopened a pop-up. The data to be displayed hasn't changed but the pop-up mediator and the view have to be recreated. My current solution is to force initialization in the mediator's onRegister() method like this:
// Inside of PopUpMediator.as
[Inject]
public var popUpModel:IPopUpModel;
[Inject]
public var popUpView:PopUpView;
override public function onRegister()
{
// Force initialization if the model hasn't changed
popUpView.foo = popUpModel.foo;
// Event based initialization
addContextListener(PopUpModelEvent_foo.CHANGE, foo_changeHandler);
}
Injecting models into mediators isn't a good idea, so I'm wondering What is the best way to init the view when its model hasn't changed?
Well,
I supose you have View1 where you have popup button.
View2 is your popus.
so when View1 button is clicked, you dispatch an event from main mediator that goes to popupCommand where you add popup to contextView, or where you remove it.
You can also have one state inside a model, that will say popupVisible and when you change that property you dispatch a event that is listened in the main mediator and that adds or removes the popup. In that case command would alter the model property instead of adding popup directly to contextView.
Third way is to add popup manually inside the view, and since the stage is being listened to by robotlegs, popup will be mediated automatically.
I've decided to add an event called PopUpViewInitEvent. A command will check if the model was updated while the pop-up was closed. If not it will reinitialize the view by dispatching the PopUpViewInitEvent. The event will contain all the data required to initialize the view. This way I won't have to inject models into my mediator.
[Inject]
public var popUpView:PopUpView;
override public function onRegister()
{
// Batch initialization
addContextListener(PopUpViewInitEvent.INIT, batchInit);
// Gradual initialization
addContextListener(PopUpModelEvent_foo.CHANGE, foo_changeHandler);
addContextListener(PopUpModelEvent_bar.CHANGE, bar_changeHandler);
}
protected function batchInit(event:PopUpViewInitEvent)
{
popUpView.foo = event.foo;
popUpView.bar = event.bar;
}

Inject a statement from the Controller

I'm writing an MVC 3 app and I have tried to code a control in the Controller (due to permissions, different menu items will be visible for different users) and use the object in the Razor page. For example, in the Controller I do something like:
public ActionResult Index()
{
var menu = "#(Html.Telerik().Menu().Name("menu").Items("menus => { menus.Add().Text("Home").Action("Index", "Home"); menus.Add().Text("Deliveries").Action("Index", "Delivery"); }))";
var model = new MenuModel()
{
Menu = menu
};
return View(model);
}
And in the View I try to render the Menu using #Model.Menu but I just get the string value rather than an actual menu. Is what I'm trying to do possible?
Extend the HtmlHelper class and use the newly created method to render your menu in the View:
Helper:
public static string RenderMenu(this HtmlHelper html)
{
var menu = new StringBuilder();
/* ... menu rendering logic ... */
return menu.ToString();
}
View:
#Html.RenderMenu();
Nevertheless, it is fine to put this logic in the view. Using the HtmlHelper extension just separates/cleans the code.
I wouldn't want to do it that way even if it were possible!
You should decouple your controller and view more than you are currently doing.
The controller should only pass data the view requires. If the view needs a menu with different menu items then use the controller to decide what menu items the view should have then add them to a list object and pass that list to the view. The view will then build a menu based on the list of menu items.
Also when I say "menu items" I don't mean markup! I mean create a new MenuItemViewModel object to persist your data between your controller and your view e.g pseudo code:
public class MenuItemViewModel { string url, string text }
List<MenuItemViewModel> menuitems ...
return View(menuitems)
Why would you want to do this? This is breaking the separation of concerns in MVC - your controller shouldn't worry about how the menu is displayed, just getting the right data to the view for display.
If you want to do security trimming, don't pass in a builder string. There are other methods available.
You could try the MVC SiteMap provider which can handle security trimming against the [Authorize] controller attributes (bit of work to learn and setup, but great once its there).
Pass in your own collection of flags or pre-build menu items, like what Greg suggested.
Make a HtmlHelper extension, something like 'IsAuthorized()' that will check against the controller authorize attributes. Here's a gist of one I used to use before switching to the MVC Sitemap.
With the html helper, you can do this:
#(Html.Telerik().Menu()
.Name("Menu")
.Items(m =>
{
#* Publicly Accessible Controller *#
m.Add()
.Text("Home").Url(Url.Action("Index", "Home"));
#* Secure Controller *#
if (Html.IsAuthorized<MyProject.Controllers.SecureController>(c => c.Index()))
{
//m.Add()....
}
.....

Creating Dialogs with Silverlight Prism

I'm new to creating Silverlight applications. I have inherited a code-base that I've been told is using Prism. Based upon what I've seen in the code-base, this looks to be true. My problem is, I am just trying to open a dialog window and close a dialog window.
To open the dialog window, I'm using the following:
UserControl myDialog = new MyDialog();
IRegion region = myRegionManager.Regions["DIALOG_AREA"];
IRegionManager popupRegionManager = region.Add(myDialog, null, true);
region.Activate(myDialog);
The dialog I have designed appears. It has two buttons: "OK" and "Cancel". When a user clicks on of these dialogs, I want to close the dialog. My problem is, I have no idea how to do this. Because the dialog is not a ChildWindow, I cannot call this.Close(). But, when I change MyDialog to a ChildWindow, it is wrapped in some custom window chrome that i can't figure out how to get rid of.
How do I close a dialog in Prism? Thank you!
Instead of putting in a different region, you should make your ViewModel call a service where you call your dialog window.
public MainPageViewModel(IMainPage view,
ILoginBox loginBox )
: base(view)
{
this.view = view;
this.loginBox = loginBox;
}
public interface ILoginBox
{
void Show();
}
remember to use the IOC container and declare on your ModuleClass:
protected override void RegierTypes()
{
base.Container.RegisterType<ILoginBox , LoginBox>();
}
I hope it helps.
If you are still looking for an example, check:
another Stackoverflow sample

Resources