How can I use RequestFactory to create an object and initialize a collection whithin it with objects retrieved from another ReqFactory? - gwt

I am struggling with an issue using RequestFactory in GWT.
I have a User object : this object has login and password fields and other fields which are of collection type.
public class User {
private String login;
private String password;
private Set<Ressource> ressources;
// Getters and setters removed for brievety
}
I need to persist this object in db so I used RequestFactory because it seems like a CRUD-type operation to me.
Now for the RequestFactory part of the code, this is how I have tried to do it :
I create a UserRequestContext object to create a request object for the new User. Which gives something like :
public interface MyRequestFactory extends RequestFactory {
UserRequestContext userRequestContext();
RessourceRequestContext ressourceRequestContext();
}
and to create the user object I have something like this :
public class UserAddScreen extends Composite {
private UserProxy userProxy;
EventBus eventBus = new SimpleEventBus();
MyRequestFactory requestFactory = GWT.create(MyRequestFactory.class);
...
public UserAddScreen() {
...
requestFactory.initialize(eventBus);
}
public showUserAddScreen() {
// Textbox for password and login
// Listbox for ressources
}
}
I have tried to implement it as a wizard. So at the beginning of the UserAddScreen, I have a
a userProxy object.
This object fields are initialized at each step of the wizard :
the first step is adding the login and password
the second step is adding ressources to the userProxy object.
for this last step, I have two list boxes the first one containing the list of all the ressources i have in my DB table Ressources that I got from RessourceRequestContext.getAllRessource (I have a loop to display them as listbox item with the RessourceId as the value) and the second allows me to add the selected Ressources from this first listbox. Here is the first listbox :
final ListBox userRessourcesListBox = new ListBox(true);
Receiver<List<RessourceProxy>> receiver = new Receiver<List<RessourceProxy>>() {
#Override
public void onSuccess(List<RessourceProxy> response) {
for(RessourceProxy ressourceProxy : response) {
ressourcesListBox.addItem(ressourceProxy.getNom() + " " + ressourceProxy.getPrenom(), String.valueOf(ressourceProxy.getId()));
}
}
};
RessourceRequestContext request = requestFactory.ressourceRequestContext();
request.getAllRessource().fire(receiver);
So, as you can see, my code loops over the retrieved proxies from DB and initializes the items within the listbox.
Here are the control buttons :
final Button addButton = new Button(">");
addButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
for (int i = 0; i < ressourcesListBox.getItemCount(); i++) {
boolean foundInUserRessources = false;
if (ressourcesListBox.isItemSelected(i)) {
for (int j = 0; j < userRessourcesListBox
.getItemCount(); j++) {
if (ressourcesListBox.getValue(i).equals(
userRessourcesListBox.getValue(j)))
foundInUserRessources = true;
}
if (foundInUserRessources == false)
userRessourcesListBox.addItem(ressourcesListBox
.getItemText(i), ressourcesListBox
.getValue(i));
}
}
}
});
So when somebody selects one or more users and click on a ">" button, all the selected items go to the second listbox which is named userRessourceListBox
userRessourcesListBox.setWidth("350px");
userRessourcesListBox.setHeight("180px");
After that, I have a FINISH button, which loops over the items in the second listbox (which are the ones I have selected from the first one) and I try to make a request (again) with RequestFactory to retrieve the ressourceProxy object and initialize the userProxy ressources collection with the result
final Button nextButton = new Button("Finish");
nextButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
RessourceRequestContext request = requestFactory.ressourceRequestContext();
for(int i = 0; i < userRessourcesListBox.getItemCount(); i++) {
Receiver<RessourceProxy> receiver = new Receiver<RessourceProxy>() {
#Override
public void onSuccess(RessourceProxy response) {
userProxy.getRessource().add(response);
}
};
request.find(Long.parseLong(userRessourcesListBox.getValue(i))).fire(receiver);
}
creationRequest.save(newUserProxy).fire(new Receiver<Void>() {
#Override
public void onSuccess(Void response) {
Window.alert("Saved");
}
});
}
});
Finally, (in the code above) I try to save the UserProxy object (with the initial request context I have created userProxy with)... but it doesn't work
creationRequest.save(newUserProxy).fire(...)
It seems like when looping over the result in the onSuccess method :
userProxy.getRessource().add(response);
I retrieve the response (of type RessourceProxy) but beyond this method, for example when I try to save the userProxy object AFTER THE LOOP, there are no RessourceProxy objects in the ressourceProxy collection of userProxy...
Have you guys ever experienced something like this ?
Perhaps I am not doing it right : do I have to get the ressource with the UserRequestContext ? so that my newUser object and ressources are managed by the same request Context ?
if yes then I think it's a little bit weird to have something mixed together : I mean what is the benefit of having a Ressource-related operation in the User-related request context.
any help would be really really ... and I mean really appreciated ;-)
Thanks a lot

The message "… has been frozen" means that the object has been either edit()ed or passed as an argument to a service method, in another RequestContext instance (it doesn't matter whether it's of the same sub-type –i.e. UserRequestContext vs. RessourceRequestContext– or not) which hasn't yet been fire()d and/or the response has not yet come back from the server (or it came back with violations: when the receiver's onViolation is called, the objects are still editable, contrary to onSuccess and onFailure).
UPDATE: you have a race condition: you loop over the resource IDs and spawn as many requests as the number of items selected by the user, and then, without waiting for their response (remember: it's all asynchronous), you save the user proxy. As soon as you fire() that last request, the user proxy is no longer mutable (i.e. frozen).
IMO, you'd better keep the RessourceProxys retrieved initially and use them directly in the user proxy before saving it (i.e. no more find() request in the "finish" phase). Put them in a map by ID and get them from the map instead of finding them back from the server again.

Related

Unity-How to Create a Dialogue Tree?

So I have this program set up for a simple dialogue tree, where I want to display a question and two options in the unity editor, and if you click one option, you either go to another level of the tree or a leaf. I want to use the composite design pattern to make separate level instances, each with different parameters for one question and two options and add them together into a list. What I'm stuck on is how do I start at the first level and traverse down the tree depending on which button I press. It seems like no matter what I do, it only displays the last level parameters added to the list. The best I can think is maybe add some sort of shifting list function during the button click events. If anyone could shoot some ideas, I would appreciate it. Thank you.
public class Level : MonoBehaviour {
bool button1Pressed;
bool button2Pressed;
private void Start()
{
Level Level1 = new Level("Hello", "Hi", "Shut Up");
Level leaf1 = new Level("Don't be Rude");
Level Level2 = new Level("What you Doing?", "Not Much", "None of your Business");
Level leaf2 = new Level("Well Excuuuuse Me");
Level Level3 = new Level("Can I do that too?", "Sure", "Go Away");
Level leaf3 = new Level("Fine. Be a Jerk");
Level Level4 = new Level("This is boring, can we do something else?", "Why not?", "You're boring");
Level leaf4 = new Level("I'll go be boring somewhere else");
Level Level5 = new Level("You want ice cream?", "Sounds Good", "I'm allergic");
Level leaf5 = new Level("ok.......");
Level leaf = new Level("I Want Chocolate");
Level1.add(Level1);
Level1.add(leaf1);
Level2.add(Level3);
Level2.add(leaf2);
Level3.add(Level4);
Level3.add(leaf3);
Level4.add(Level5);
Level4.add(leaf4);
Level5.add(leaf5);
Level5.add(leaf);
}
public static Text Textbox;
public static Button Button1;
public static Button Button2;
public string OptionA;
public string OptionB;
public string Question;
public string Leaf;
private List<Level> levels;
public Level(string question, string optionA, string optionB)
{
this.Question = question;
this.OptionA = optionA;
this.OptionB = optionB;
GameObject.FindGameObjectWithTag("Level").GetComponentInChildren<Text>().text = Question;
GameObject.FindGameObjectWithTag("OptionA").GetComponentInChildren<Text>().text = OptionA;
GameObject.FindGameObjectWithTag("OptionB").GetComponentInChildren<Text>().text = OptionB;
levels = new List<Level>();
}
public Level(string leaf)
{
this.Leaf = leaf;
Textbox.text = leaf;
}
public void add(Level lvl)
{
levels.Add(lvl);
}
public List<Level> getLevels()
{
return levels;
}
public void Button1Pressed()
{
}
public void Button2Pressed()
{
}
}
public class Initializer : MonoBehaviour {
public Text Textbox;
public Button Button1;
public Button Button2;
void Awake()
{
Level.Textbox = this.Textbox;
Level.Button1 = this.Button1;
Level.Button2 = this.Button2;
}
}
The short answer: All nodes know their parent and children.
There are a few ways to approach this problem. I'll explain an approach using a tree structure with multiple node classes. First we can examine the interactions with the player as you've outlined:
Making a dialog choice (speaking)
Observing a dialog response (listening)
There's also some important conditions we need to consider:
User terminated conversations
AI terminated conversations
From this outline we can build our tree with some classes. I've scratched out some examples but I haven't tested them. Hopefully it conveys the idea and you can build your own solution. It may also be more useful for you to make a single Node class that just knows which type it is. Another improvement would be using an interface or some way to generalize the parent/child relationship which would allow for more complicated tree structures.
class ChoiceNode
{
public ChoiceNode(ResponseNode myParent)
{
parent = myParent;
}
ResponseNode parent = null;
List<ResponseNode> children = new List<ResponseNode>;
bool canSayGoodbye = true;
}
class ResponseNode
{
public ResponseNode(ChoiceNode myParent, string myMessage)
{
parent = myParent;
parent.children.Add(this);
response = myMessage;
}
ChoiceNode parent;
ChoiceNode child;
string response;
}
We should now be able to use a method to display dialog choice by simply enumerating the ResponseNode.children. Then whenever we make a dialog choice we want to display the ResponseNode.response and then move to the ResponseNode.child to find the next set of dialog choices. When parent == null we're at the root branch. When child == null we display some termination text.
I hope this is helpful and gives you some ideas.
Ok, I tried to understand the logic of your code but there are several things that I don't understand, maybe it's better if I try to explain my "Unity dialogue tree".
First:
We need to create a Tree object. If you just want a binary tree you just need tree variables:
public class BinaryTree{
private string root;
private BinaryTree left;
private BinaryTree right
getters/setters
}
this object don't even need to be a Unity component.
root is "dialog"
left is "optionA"
right is "optionB"
if you don't need multiple answer just make left=right.
If you need a way to identify multiple answer I suggest you to create an object like this:
public class Tree{
private Dictionary<string,string> root;
private List<Tree> next;
getters/setters
}
root is again your dialog. A key (that identify your answer) and a value that is the actual dialog.
next is a List of Tree (you identify your answer by making a loop in your next and by checking the key of the dictionary).
Now in the Start method you need to create a new Tree object and set your nexts.
Example
Start(){
BinaryTree bn = new BinaryTree();
bn.Root = "Is this a question?";
BinaryTree left = new BinaryTree();
left.Root = "Nope";
bn.Left = left;
BinaryTree right = new BinaryTree();
right.Root = "Yes it is...";
bn.Right = right;
}
Almost the same for the Tree version:
Start(){
Tree bn = new Tree();
bn.Root = new Dictionary<string, string>();
bn.Root.Add("key1", "Do you need something?");
bn.Next = new List<Tree>();
Tree answer1 = new Tree();
answer1.Root = new Dictionary<string, string>();
answer1.Root.Add("key2", "Yes");
bn.Next.Add();
... iterate...
}
Of course this is just a basic example. The best way to initialize that is to add your dialog to an array and iterate.
Anyway.
Now you can (for example) create a button. In the Start you can Initialize it's text value to your root. In the PointerDown/Clicked method you can make an array of the possible answer keys and, for example, you can decide to generate multiple buttons for multiple answers (or just use 2 static button answer with the BinaryTree) and change the text based on the answer root value (or left/right value). each of the answer button should, in the PointerDown/Clicked method, send the key value (or left/right object) of your user selection (in practice the next value that will display in the main question button).
Of course the "question button" once clicked again should display the next of your answer (and maybe you can decide to add a bool question variable to your object... or maybe you can just decide to use only the left side for a question... or maybe you can decide that if there is only 1 value in the next List that value is a question and it should just show it in the main button text value).
And if the next is null of course you can end the conversation.
There are multiple ways to do this.

Bukkit How to change an int in the config file then be able to change it again without reloading (Custom config file class.))

Okay so I am making a custom feature for my OP-Prison server, one of the things that I need to do is get an integer from the players.yml file, check if it is >= one, if it is take away one, save it and then if it is still above one then they can repeat the action untill it's 0.
The issue comes with the fact that I have to restart the server for the file to change, and even when I do, it will only go down by one integer at a time, before having to reload it again.
GUI Creation code:
Main main = Main.getPlugin(Main.class);
#SuppressWarnings("unused")
private FileControl fc;
#SuppressWarnings("unused")
private FileControl playerfc;
public static String inventoryname = Utils.chat(Main.pl.getFileControl().getConfig().getString("Backpacks.White.InventoryName"));
public List<Player> WhiteOpened = new ArrayList<>();
public static Inventory whiteBackpack(Player player) {
Inventory whiteBackpack = Bukkit.createInventory(null, 27, (inventoryname));
UUID uuid = player.getUniqueId();
whiteBackpack.setItem(10,
new ItemCreator(Material.INK_SACK).setData(8)
.setDisplayname(Utils.chat("&fCommon Packages &8» &f&l" + Main.pl.getPlayerFile().getConfig().getInt("Users." + uuid + ".Packages.Common")))
.getItem());
return whiteBackpack;
}
Code for updating the config + item when the Commonpackage is clicked:
#EventHandler
public void whiteBackpackInteract(InventoryClickEvent event) {
Player player = (Player) event.getWhoClicked();
UUID uuid = player.getUniqueId();
ItemStack clicked = event.getCurrentItem();
String title = event.getInventory().getName();
if (title.equals(inventoryname)) {
// Making it so that the item cannot be moved
event.setCancelled(true);
if (clicked != null) {
if (event.getSlot() == 10) {
// Getting the user's common packages section in the config and checking if it is greater than or equal to 1.
if (Main.pl.getPlayerFile().getConfig().getInt("Users." + uuid + ".Packages.Common") >= 1) {
// Saving the user's common package section to 'currentCommon'
Integer currentCommon = Main.pl.getPlayerFile().getConfig().getInt("Users." + uuid + ".Packages.Common");
// Taking away one from 'currentCommon' and saving it to 'newCommon'
Integer newCommon = currentCommon - 1;
// Getting the 'players.yml' file
File file = new File(main.getDataFolder(), "players.yml");
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
// Checking if the current common keys is greater than or equal to 1
if (currentCommon >= 1) {
try {
//Now, Here's where the error lies.
//Gets the player's common package count and sets it to the 'newCommon' count
config.set("Users." + uuid + ".Packages.Common", newCommon);
//Saves the players.yml file
config.save(file);
} catch (IOException e) {
e.printStackTrace();
}
// Updates the inventory they're currently in (Atleast it's meant to...)
player.updateInventory();
// Sends them a message (This is just for testing purposes, making sure it's working.)
player.sendMessage(Utils.chat("&8(&9Vexil&8) &fCommon Package"));
}
}
}
}
}
}
If there is any other code that you need, just ask I'll happily provide it for you.
Right now, you need to restart the server for it to save the data to the file. This should not happen, since you are calling the method config.save(file). The following is simply speculation, but it's the only cause that I think can easily explain what is going on.
In the object that is returned by getPlayerFile().getConfig(), there is likely a variable that stores a FileConfiguration object. That variable houses all the data from the players.yml file. In your whiteBackpackInteract() method, you load the data all over again. You then continue on to write to this NEW FileConfiguration variable, rather than the one that is stored in getPlayerfile().getConfig(). Since you then proceed to save to the file directly, the variables stored in the getPlayerfile().getConfig() is never told that you changed some values around. To fix this, you need to change the following:
config.set("Users." + uuid + ".Packages.Common", newCommon);
config.save(file);
to this:
Main.pl.getPlayerFile().getConfig().set("Users." + uuid + ".Packages.Common", newCommon);
Main.pl.getPlayerFile().getConfig().save(file);
and then delete this line of code:
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
This should solve your problem entirely. If it does not, I would recommend not using your friend's custom config API and instead just use the ones that are built in. Using third party code that you don't properly understand can very often lead to problems such as this.
The following are not the bugs, but are suggestions to help improve your code:
You should be sure to put your comments ABOVE or to the RIGHT over the code they describe. People read from top to bottom, so the comments (before I made the suggested edit to your post) were all below the code they describe.
Typically, you want to try to make sure that if code doesn't need to be run, it isn't. Since the int newCommon is not used until inside that if statement, you should move it in there.
You are using Main.getPlugin();
Now while that doesn't seem like such a bad thing, your getting an unassigned variable, I have no idea how it is working but you're assigning Main to Main. There are 2 proper ways to actually get the main class.
The first, and generally best way, is to use dependency injection.
So basically,
public class Main extends JavaPlugin {
#Override
public void onEnable() {
BackpackListener listener new Backpacklistener(this);
getServer().getPluginManager().registerEvents(listener, this);
}
}
public class BackpackListener implements Listener {
private Main instance;
private BackpackUtil util;
public BackpackListener(Main instance) {
this.instance = instance;
util = new BackpackUtil();
}
#EventHandler
public void onClick(InventoryClickEvent event) {
//code
util.whiteBackpack(instance);
}
public class BackpackUtil {
public Inventory whiteBackpack(Main instance) {
FileConfiguration config = instance.getConfig();
//Do things
instance.saveConfig();
}
}
The next way you can do it is less optimal, and frowned upon, but still an easier option.
public class Main() {
public static Main instance;
#Override
public void onEnable() {
instance = this;
}
}
public class ConfigHelper() {
Main instance = Main.instance;
FileConfiguration config = instance.getConfig();
//Do things
instance.saveConfig();
}
It's good to get out of the habit of using the second method (It's called a singleton), because normally the main class will change, or have multiple instances, etc... but with Spigot there can only be one main instance and one thread.

How to validate inputs and prevent save actions using databinding in eclipse?

I want to create input forms which validate user input and prevent the model from being saved with invalid data. I have been using databinding which works up to a point but my implementation is not as intuitive as I would like.
Imagine an input which contains '123' and the value must not be empty. The user deletes the characters one by one until empty. The databinding validator shows an error decoration.
However, if the user saves the form and reloads it, then a '1' is displayed in the field - i.e. the last valid input. The databinding does not transmit the invalid value into the model.
I have a ChangeListener but this is called before the databinding so at that point the invalid state has not been detected.
I would like the error to be displayed in the UI but the model remains valid (this is already so). Also, for as long as the UI contains errors, it should not be possible to save the model.
/**
* Bind a text control to a property in the view model
**/
protected Binding bindText(DataBindingContext ctx, Control control,
Object viewModel, String property, IValidator validator)
{
IObservableValue value = WidgetProperties.text(SWT.Modify).observe(
control);
IObservableValue modelValue = BeanProperties.value(
viewModel.getClass(), property).observe(viewModel);
Binding binding = ctx.bindValue(value, modelValue, getStrategy(validator), null);
binding.getTarget().addChangeListener(listener);
ControlDecorationSupport.create(binding, SWT.TOP | SWT.LEFT);
return binding;
}
private UpdateValueStrategy getStrategy(IValidator validator)
{
if (validator == null)
return null;
UpdateValueStrategy strategy = new UpdateValueStrategy();
strategy.setBeforeSetValidator(validator);
return strategy;
}
private IChangeListener listener = new IChangeListener()
{
#Override
public void handleChange(ChangeEvent event)
{
// notify all form listeners that something has changed
}
};
/**
* Called by form owner to check if the form contains valid data e.g. before saving
**/
public boolean isValid()
{
System.out.println("isValid");
for (Object o : getDataContext().getValidationStatusProviders())
{
ValidationStatusProvider vsp = (ValidationStatusProvider) o;
IStatus status = (IStatus)vsp.getValidationStatus()
.getValue();
if (status.matches(IStatus.ERROR))
return false;
}
return true;
}
Your best bet is to steer clear of ChangeListeners - as you've discovered, their order of execution is either undefined or just not helpful in this case.
Instead, you want to stick with the 'observable' as opposed to 'listener' model for as long as possible. As already mentioned, create an AggregateValidationStatus to listen to the overall state of the DataBindingContext, which has a similar effect to your existing code.
Then you can either listen directly to that (as below) to affect the save ability, or you could even bind it to another bean.
IObservableValue statusValue = new AggregateValidationStatus(dbc, AggregateValidationStatus. MAX_SEVERITY);
statusValue.addListener(new IValueChangeListener() {
handleValueChange(ValueChangeEvent event) {
// change ability to save here...
}
});
You can use AggregateValidationStatus to observe the aggregate validation status:
IObservableValue value = new AggregateValidationStatus(bindContext.getBindings(),
AggregateValidationStatus.MAX_SEVERITY);
You can bind this to something which accepts an IStatus parameter and it will be called each time the validation status changes.

GWT RequestFactory + CellTable

Does anyone know for an example of GWT's CellTable using RequestFactory and that table is being edited? I would like to list objects in a table (each row is one object and each column is one property), be able to easily add new objects and edit. I know for Google's DynaTableRf example, but that one doesn't edit.
I searched Google and stackoverflow but wasn't able to find one. I got a bit confused with RF's context and than people also mentioned some "driver".
To demonstrate where I currently arrived, I attach code for one column:
// Create name column.
Column<PersonProxy, String> nameColumn = new Column<PersonProxy, String>(
new EditTextCell()) {
#Override
public String getValue(PersonProxy person) {
String ret = person.getName();
return ret != null ? ret : "";
}
};
nameColumn.setFieldUpdater(new FieldUpdater<PersonProxy, String>() {
#Override
public void update(int index, PersonProxy object, String value) {
PersonRequest req = FaceOrgFactory.getInstance().requestFactory().personRequest();
PersonProxy eObject = req.edit(object);
eObject.setName(value);
req.persist().using(eObject).fire();
}
});
and my code for data provider:
AsyncDataProvider<PersonProxy> personDataProvider = new AsyncDataProvider<PersonProxy>() {
#Override
protected void onRangeChanged(HasData<PersonProxy> display) {
final Range range = display.getVisibleRange();
fetch(range.getStart());
}
};
personDataProvider.addDataDisplay(personTable);
...
private void fetch(final int start) {
lastFetch = start;
requestFactory.personRequest().getPeople(start, numRows).fire(new Receiver<List<PersonProxy>>() {
#Override
public void onSuccess(List<PersonProxy> response) {
if (lastFetch != start){
return;
}
int responses = response.size();
if (start >= (personTable.getRowCount()-numRows)){
PersonProxy newP = requestFactory.personRequest().create(PersonProxy.class);
response.add(newP);
responses++;
}
personTable.setRowData(start, response);
personPager.setPageStart(start);
}
});
requestFactory.personRequest().countPersons().fire(new Receiver<Integer>() {
#Override
public void onSuccess(Integer response) {
personTable.setRowCount(response+1, true);
}
});
}
I try to insert last object a new empty object. And when user would fill it, I'd insert new one after it. But the code is not working. I says that user is "attempting" to edit a object previously edited by another RequestContext.
Dilemmas:
* am I creating too many context'es?
* how to properly insert new object into celltable, created on the client side?
* on fieldUpdater when I get an editable object - should I insert it back to table or forget about it?
Thanks for any help.
am I creating too many context'es?
Yes.
You should have one context per HTTP request (per fire()), and a context that is not fire()d is useless (only do that if you/the user change your/his mind and don't want to, e.g., save your/his changes).
You actually have only one context to remove here (see below).
Note that your approach of saving on each field change can lead to "race conditions", because a proxy can be edit()ed by at most one context at a time, and it remains attached to a context until the server responds (and once a context is fired, the proxy is frozen –read-only– also until the server responds).
(this is not true in all cases: when onConstraintViolation is called, the context and its proxies are unfrozen so you can "fix" the constraint violations and fire the context again; this should be safe because validation is done on the server-side before any service method is called).
how to properly insert new object into celltable, created on the client side?
Your code looks OK, except that you should create your proxy in the same context as the one you'll use to persist it.
on fieldUpdater when I get an editable object - should I insert it back to table or forget about it?
I'm not 100% certain but I think you should refresh the table (something like setRowData(index, Collections.singletonList(object)))
BTW, the driver people mention is probably the RequestFactoryEditorDriver from the Editor framework. It won't help you here (quite the contrary actually).

MVVM Toolkit light Messenger chained Messages

this might be complicated to explain but I give it a try...
I would like to use the Messenger to navigate to a new Page and also create a new Object (or pass one). How is this possible or am I on the wrong path?
Basically:
Click on "add new person" button which should bring up the PersonView and also should hold a new instance of a person object.
Click on "add person" button which should bring up the same PersonView page and should receives the object which is selected.
Message 1 = open Uri / Message 2 send exisiting or new object.
So far I have MainPageViewModel which sends
Messenger.Default.Send<Uri>(...)...
And MainPage.cs which Registers Messenger.Default.Register<Uri>(...)and executes
Frame.Navigate(...targetUri)....
I tryed to Send a message to the PersonViewModel right after Frame.Navigate... but this runs out of sync... so the page was not loaded to receive the PersonMessage,...
So any tips, tricks, licks, approaches would be greate...
Thanks...
Hope this helps, basicaly it is a simple Singleton class that gets the navigation frame the page that contains, after that you are able to use it in your viewmodel and navigate, and get notified when the page changes, so with this you control in a better way the navigation
and send messages, and get aware about your page status.
public class NavigationFrameController {
private static NavigationFrameController _instance;
private MainPage _root;
public Frame NavFrame { get; set;}
private static object keyLock = new Object();
NavigationFrameController() {
_root = (MainPage)Application.Current.RootVisual;
NavFrame = _root.ContentFrame;
NavFrame.Navigated += new NavigatedEventHandler(ContentFrame_Navigated);
NavFrame.NavigationFailed += new NavigationFailedEventHandler(ContentFrame_NavigationFailed);
}
public static NavigationFrameController Instance {
get {
if (_instance == null)
lock (keyLock) {
_instance = new NavigationFrameController();
}
return _instance;
}
}
public void NavigateTo(Uri uri) {
NavFrame.Source = uri;
}
private void ContentFrame_Navigated(object sender, NavigationEventArgs e) {
//send your message
// get attached to this event and get notified
}
private void ContentFrame_NavigationFailed(object sender, NavigationFailedEventArgs e) {
}
I had the same problem - essentially where you receive the message to open a new window also create the viewmodel and add it to the view as datacontext. When you instantiate your viewmodel pass in the existing object or null etc... then in your viewmodel you can handle whether its a new or existing object.
If you are using dependency injection then call a resolve from the codebehind where you process the 'add person' messsage etc..
I think what you should do is keep the first message for navigation, and add to it the information about the object (person) you want to send. you can add a parameter to the query string, say "add=true" and then you can create the object normally in the view model, or the ID of the object to edit, and in this case, the viewmodel can retrieve the object itself and edit it.
To achieve this, in the code behind of the PersonView (associated with the PersonViewModel) has to send a message upon navigation (OnNavigatedTo) to its ViewModel containing the received info from the query string.