State persistence between child views and controllers - javafx-8

I have a Java FX application with has a main view (FXML) and its controller. I am trying to simulate a tabpane like child views(FXML) with their own controllers. So far so good, I am able to switch between the child views with the following code :
//page type para is the path of FXML file
private void changePage(String pageType) throws IOException
{
childScene.getChildren().clear();
FXMLLoader loader = new FXMLLoader(getClass().getResource(pageType));
loader.setControllerFactory((Class<?> controllerClass) ->
{
//trying to load a single instance controller of the child
if (controllerClass == EngTaiController.class)
{
eng2TaiController.setData(words, selectedFont);
return eng2TaiController;
}
try
{
return controllerClass.newInstance();
}catch (Exception ex)
{
throw new RuntimeException(ex);
}
});
Parent page = loader.load();
VBox.setVgrow(page, Priority.ALWAYS);
//childScene is the container for childviews
childScene.getChildren().add(page);
}
The problem is that it doesn't persist any user state, text in textboxes, selection in list view, loaded custom controls in child views when switched from one view to another.

Related

MVVM: OnBindingContextChange: PropertyChanged not firing in new view model

I am coding a Xamarin app and doing my best to adhere to MVVM, which I actually really like
I commonly have ContentPages containing references to Views.
I set the binding context to a VM in the Page, and then make use of OnBindingContextChanged in the view
This allows me to use PropertyChanged method to then respond to display logic conditions for my View
I've used it several times successfully but I am baffled why an additional implementation isn't working
Page looks like this
public partial class BindingTextPage : ContentPage
{
public BindingTextPage()
{
InitializeComponent();
this.BindingContext = new ViewModels.LocationsViewModel();
}
}
View looks like this
private LocationsViewModel_vm;
public BindingTestView()
{
InitializeComponent();
System.Diagnostics.Debug.WriteLine("Debug: Initialised BindingTesView view");
}
protected override void OnBindingContextChanged()
{
System.Diagnostics.Debug.WriteLine("Debug: BindingTest: OnBindingContextChanged: Context " + this.BindingContext.GetType());
_vm = BindingContext as LocationsViewModel;
_vm.PropertyChanged += _vm_PropertyChanged;
}
private void _vm_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
try
{
System.Diagnostics.Debug.WriteLine("Debug: BindingTest: Method called");
System.Diagnostics.Debug.WriteLine("Debug: BindingTest: Property " + e.PropertyName);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Debug: BindingTestView: Error changing context " + ex.Message);
}
}
Extract of view model, very simply in this case setting a string and hence changing a property, which I would have expected would then cause PropertyChange to fire?
public LocationsViewModel()
{
tempMessage = "this is from the view model";
}
public string tempMessage
{
get
{
return _tempMessage;
}
set
{
_tempMessage = value;
OnPropertyChanged(nameof(tempMessage));
}
}
My debug statements when it boots up shows that OnBindingContextChange is being called, but in this one instance _vm_PropertyChanged never fires? I'd expect tempMessage being set to do so?
The order of events in your code is the following
Constructor of LocationsViewModel is called
From your constructor, you are setting tempMessage
The setter of tempMessage calls OnPropertyChanged, since the event is null at the time being, it's not fired
Constructor of LocationsViewModel is left
Page.BindingContext is set
OnBindingContextChanged is called
LocationsViewModel.PropertyChanged is subscribed by your page
Since the event is raised (or it's tried to) before your page subscribes to, your page simply does not get informed about the event being raised. If you set the value after the event has been subscribed to, the handler will be called as expected.
e.g.
protected override void OnBindingContextChanged()
{
_vm = BindingContext as LocationsViewModel;
_vm.PropertyChanged += _vm_PropertyChanged;
_vm.tempMessage = "Hello, world!"; // clichée , I know
}

Menu item not cast node javafx scene

I am making a JavaFX app. I have create a Menuitem About. Upon clicking on the About Menuitem it will display a new window with some info about my app. The window is a Anchor Pane with custom close button. I have set the stage undercoated at run time. I want to close this window without closing my main application. I don't want to set its visibility turn off on method call. I see some solution in net like Window existingWindow = ((Node) event.getSource()).getScene().getWindow(); but i can't use this as am getting error similar to this Menu item not cast node javafx scene. How can I achieve this goal?
Actually this is not my own answer, but I managed to understand #James_D. I usually create to manage open windows, otherwise I have to write a lot of code And this is solution code.
In your controller class:
#FXML
void openAnotherWindow(ActionEvent actionEvent) {
try {
OpenWindow.openWindowMenuItem(someLabel, "views/some.fxml", "Title", 600, 400,
false, "resources/pictures/some_icon.png");
} catch (IOException e) {
e.printStackTrace();
}
}
And OpenWindow class
public class OpenWindow {
public static void openWindowMenuItem(Node label, String recource, String title,
int width, int height, boolean resizeable, String icon) throws IOException {
Parent root = FXMLLoader.load(Objects.requireNonNull(OpenWindow.class.getClassLoader().getResource(recource)));
Stage stage = new Stage();
Scene scene = new Scene(root, width, height);
if (icon != null) {
stage.getIcons().add(new Image(icon));
}
stage.setTitle(title);
stage.setResizable(resizeable);
stage.setScene(scene);
stage.show();
// close current window
label.getScene().getWindow().hide(); // this is key point
}
}
You can do it event without extra class. Hope it will help and again thanks to #James_D

Exception for listview adapter when listview is missing(Fragment)

1) My Activity has many fragment, the fragments can switch to another(whole rootView change) by navigation drawer.
2) I have a listView in a fragment that update using simple adapter.
3) If I switch the fragment to another while listView is still loading.
-An error occur.
May i know how to make a exception for this??
I extend the simple adapter for listView
ListAdapter adapter = new ExtendedSimpleAdapter(
getActivity(), news_List,
R.layout.news_item, new String[]{"news", "datetime", "desc","day"},
new int[]{R.id.news, R.id.new_time, R.id.desc,R.id.day});
setListAdapter(adapter);
This is the custom adapter :
public class ExtendedSimpleAdapter extends SimpleAdapter{
Context context2;
public ExtendedSimpleAdapter(Context context, List<? extends Map<String, String>> data, int resource, String[] from, int[] to){
super(context, data, resource, from, to);
context2=context;
}
public View getView(int position, View convertView, ViewGroup parent){
// here you let SimpleAdapter built the view normally.
View v = super.getView(position, convertView, parent);
// Then we get reference for Picasso
ImageView img = (ImageView) v.getTag();
TextView txt = (TextView) v.findViewById(R.id.state);
if(img == null){
img = (ImageView) v.findViewById(R.id.new_pic);
v.setTag(img);
}
// get the url from the data you passed to the `Map`
String url = ((Map<String, String>)getItem(position)).get("pic");
String type = ((Map<String, String>)getItem(position)).get("type");
String state = ((Map<String, String>)getItem(position)).get("state");
if (type.equals("reward"))
{
txt.setVisibility(View.VISIBLE);
}
else
{
txt.setVisibility(View.GONE);
}
if(url.equals("")){
Picasso.with(context2).load(R.drawable.default).into(img);
}
else{
Picasso.with(context2)
.load(url)
.placeholder(R.drawable.default)
.resize(100, 100)
.centerCrop()
.into(img);
}
// return the view
return v;
}
}
The error message NullPointerException
E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.werebits.chopinkmerchant, PID: 14486
java.lang.NullPointerException
at android.widget.SimpleAdapter.<init>(SimpleAdapter.java:85)
at com.werebits.chopinkmerchant.ExtendedSimpleAdapter.<init>(ExtendedSimpleAdapter.java:19)
at com.werebits.chopinkmerchant.Home$PlaceholderFragment$4.onSuccess(Home.java:488)
at com.loopj.android.http.JsonHttpResponseHandler$1$1.run(JsonHttpResponseHandler.java:128)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5230)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:780)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:596)
at dalvik.system.NativeStart.main(Native Method)
Is this means when i switch fragment when the listView is still loading, the adapter can't find the listview??
how to add exception for this???

Render view based on another view in Eclipse plugin

I am developing an Eclipse plug-in that has currently 2 views. In my first view I have a list of connections displayed in a TableViewer (name and connection status).In my second view I want to load the tables in a database (the connection). This loading will be done by clicking a menu item on a connection ("view details"). These tables will be displayed in a TreeViewer because they can also have children. I have tried to do it this way:
My View class:
public class DBTreeView extends ViewPart {
private TreeViewer treeViewer;
private Connection root = null;
public DBTreeView() {
Activator.getDefault().setDbTreeView(this);
}
public void createPartControl(Composite parent) {
treeViewer = new TreeViewer(parent);
treeViewer.setContentProvider(new DBTreeContentProvider());
treeViewer.setLabelProvider(new DBTreeLabelProvider());
}
public void setInput(Connection conn){
root = conn;
treeViewer.setInput(root);
treeViewer.refresh();
}
}
I made a setInput method that is called from the action registered with the menu item in the connections view with the currently selected connection as argument:
MViewContentsAction class:
public void run(){
selectedConnection = Activator.getDefault().getConnectionsView().getSelectedConnection();
Activator.getDefault().getDbTreeView().setInput(selectedConnection);
}
In my ContentProvider class:
public Object[] getChildren(Object arg0) {
if (arg0 instanceof Connection){
return ((Connection) arg0).getTables().toArray();
}
return EMPTY_ARRAY;
}
where EMPTY_ARRAY is an...empty array
The problem I'm facing is that when in debug mode, this piece of code is not executed somehow:
Activator.getDefault().getDbTreeView().setInput(selectedConnection);
And also nothing happens in the tree view when clicking the menu item. Any ideas?
Thank you
Huh. Ok, what you're doing here is.. not really the right way. What you should be doing is registering your TableViewer as a selection provider.
getSite().setSelectionProvider(tableViewer);
Then, define a selection listener and add it to the view with the tree viewer like this:
ISelectionListener listener = new ISelectionListener() {
public void selectionChanged(IWorkbenchPart part, ISelection sel) {
if (!(sel instanceof IStructuredSelection))
return;
IStructuredSelection ss = (IStructuredSelection) sel;
// rest of your code dealing with checking whether selection is what is
//expected and if it is, setting it as an input to
//your tree viewer
}
};
public void createPartControl(Composite parent) {
getSite().getPage().addSelectionListener(listener);
}
Now your tree viewer's input will be changed according to what is selected in the table viewer (btw, don't forget to call treeviewer.refresh() after you set new input).
See an example here.

MVVM using Page Navigation On Windows Phone 7

The Navigation framework in Windows Phone 7 is a cut down version of what is in Silverlight. You can only navigate to a Uri and not pass in a view. Since the NavigationService is tied to the View, how do people get this to fit into MVVM. For example:
public class ViewModel : IViewModel
{
private IUnityContainer container;
private IView view;
public ViewModel(IUnityContainer container, IView view)
{
this.container = container;
this.view = view;
}
public ICommand GoToNextPageCommand { get { ... } }
public IView { get { return this.view; } }
public void GoToNextPage()
{
// What do I put here.
}
}
public class View : PhoneApplicationPage, IView
{
...
public void SetModel(IViewModel model) { ... }
}
I am using the Unity IOC container. I have to resolve my view model first and then use the View property to get hold of the view and then show it. However using the NavigationService, I have to pass in a view Uri. There is no way for me to create the view model first. Is there a way to get around this.
Instead of passing the view through the constructor. You could construct the view first via the NavigationService and pass it into the view-model. Like so:
public class ViewModel : IViewModel
{
private IUnityContainer container;
private IView view;
public ViewModel(IUnityContainer container)
{
this.container = container;
}
public ICommand GoToNextPageCommand { get { ... } }
public IView
{
get { return this.view; }
set { this.view = value; this.view.SetModel(this); }
}
public void GoToNextPage()
{
// What do I put here.
}
}
PhoneApplicationFrame frame = Application.Current.RootVisual;
bool success = frame.Navigate(new Uri("View Uri"));
if (success)
{
// I'm not sure if the frame's Content property will give you the current view.
IView view = (IView)frame.Content;
IViewModel viewModel = this.unityContainer.Resolve<IViewModel>();
viewModel.View = view;
}
If you are using Mvvm Light you could try:
Windows Phone 7 — Navigation between pages using MVVM Light Messaging
(See similar post: Silverlight Navigation using Mvvm-light(oobe)+MEF?)
My opinion is that the view-model should be created and registered at application startup. By placing it inside the root DataContext all pages will automatically get a reference to it without any code-behind or IoC tricks.
// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
m_ViewModel = new PrimaryViewModel(RootFrame) ;
RootFrame.DataContext = m_ViewModel;
}
// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
m_ViewModel = new PrimaryViewModel(RootFrame) ;
m_ViewModel.Activated(PhoneApplicationService.Current.State);
RootFrame.DataContext = m_ViewModel;
}
If you are using MVVM architecture,then you can pass navigationPage after registering using Messenger. Create a model class (say NavigateToPageMessage) with a string(say PageName) variable. You want to pass string from homepage.xaml to newpage.xaml,then in Homepage viewmodel just send the message like this under the command you binded (say HomeNavigationCommand)
private void HomeNavigationCommandHandler()
{
Messenger.Default.Send(new NavigateToPageMessage {PageName = "newpage"});
}
In the newpage Viewmodel,you should register the messenger like this,
Messenger.Default.Register<NavigateToPageMessage>(this, (action) => ReceiveMessage(action));
private object ReceiveMessage(NavigateToPageMessage action)
{
var page = string.Format("/Views/{0}.xaml", action.PageName);
NavigationService.Navigate(new System.Uri(page,System.UriKind.Relative));
return null;
}
//Assuming your views are in View Folder