First View is a listview including some items.
eg. item0, item1, item2
When click a "new" button, the second view will be shown.
Then click a "save" button and input a name(eg. item3). The "item3" will be save in seconde viewmodel
After saving success. The first view will refresh and show "item3"
How to pass name "item3" from second viewmodel to first viewmodel for showing in first view?
Use the Messenger Plugin in MVVMCross. The First VM registers for a message. The 2nd VM Publishes(Notifies) the 1st one by publishing a Message of the Message type(class) registered. For example:
1st VM
In order to use the messenger plugin you need to register it first, using Injection as follows:
private readonly IMvxMessenger _messenger; // VM Instance var
private MvxSubscriptionToken myToken;
public FirstViewModel(IDataService dataService, IMvxMessenger messenger)
{
// Registers the DataService(SQLite Plugin) and the Messenger Service Plugin
_dataService = dataService;
_messenger = messenger;
// Suscribe to the Meessage and pass it the name of a method to be called when the 2nd VM(the Publisher), publishes(notifies) a MyMessage to all Subscribers...
myToken = _messenger.Subscribe<MyMessage>( OnMyMessageArrived );
}
private void OnMyMessageArrived( MyMessage p_myMessage )
{
// Set the 1st VM's property from p_myMessage
MyProperty = p_myMessage.Item3
}
And the MyMessage class should look like this:
public class MyMessage : MvxMessage
{
public MyMessage( object sender, string p_item3 ): base( sender )
{
Item3 = p_item3;
}
public string Item3 { private set; get; }
}
And the 2nd VM, when ready to send the value back, it should Publish the MyMessage with the value to be passed back to 1st VM(and any subscribers) as follows:
_messenger.Publish( new MyMessage( this, item3 ) );
It's a good practice to have the Subscribers to unsubscribe from any messages they subscribe to, so back in the 1st VM, perhaps just before you close it you should do:
_messenger.Unsubscribe<MyMessage>( myToken );
For a more complete example checkout Stuart's N=9 video and associated sample. Here's the link to both:
http://slodge.blogspot.com/2013/05/n9-getting-message-n1-days-of-mvvmcross.html
R,
Pap
I'm not familiar with mvvmcross, so I'll answer your question using general M/VM/V/C architecture patterns:
Views and ViewModels shouldn't be aware of each other, that's the Controller's job. Your FirstView should alert its controller that the user clicked New and it is the controller's responsibility to then create and manage your SecondView.
The process would be like this (abstract, psuedocode-ish):
void ControllerAction() {
using(View listView = new MyListView() ) {
ListViewViewModel listViewVM = CreateListViewVM();
listViewVM.NewClicked = () => this.NewClicked(listViewVM); // wiring-up ViewModel events via anonymous methods
listView.SetViewModel( listViewVM );
listView.ShowModal();
}
}
void NewClicked(ListViewViewModel parentViewViewModel) {
using(View newEntryView = new NewEntryView()) {
if( newEntryView.ShowDialog() == DialogResult.OK ) {
parentViewViewModel.Refresh();
parentViewViewModel.SelectedItem = newEntryView.NewItemText; // passing the new item's name
}
}
}
Related
I have been through the tutorial on how to create mobile apps with azure and was able to deploy my middle ware and connect to a table called "todoitem" (which was from the tutorial) and I was able to write data to it and read from it.
(https://learn.microsoft.com/en-us/azure/developer/mobile-apps/azure-mobile-apps/quickstarts/maui/?pivots=vs2022-windows)
I was also able to connect with SSMS to the db and see the table and the items there.
Now I wanted to create my own table (useritem).
I copied everything from the tutorial, and tried creating data on my server but was returned: 500 internal server error.
I tried everything, but never was my table created, nor could I write to it.
Then I thought:
I can just create a table in SSMS via console. So I wrote a create table statement, that looked identical to the table form the todo items, only that I also added "email" as a property.
This went through fine and I saw my table in the tree in SSMS.
I could also query data from it (all from SSMS).
When I now tried to write data to my table from my app
await _table.InsertItemAsync(item);
I was finally no longer given "internal server error".
And via SSMS I could see that my data was put on the server!
Yeiks,
No where in the tutorial did it mention that I had to set the tables up myself but anyway, things where working..
UNTIL...
I tried quering data FROM the table FROM my app (not via SSMS, that works fine).
SO I did what I already did in the tutorial which was:
return await _table.GetAsyncItems().ToListAsync();
Which when done with the TODO item s(from the tutorial) returned a list with all items on that table.
BUt when I do it now with my own table, the app crashes, I dont even get an error message, it just doesnt continue.
So my guess it, that somehow, I missed setting up the connection to the table write.
With my workaround of creating the table in SSMS I was able to write to it from my app, but still cannot get my data returned.
What part of the tutorial was I missing?
Here is some of the code in question that should be important:
program.cs:
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("C5"); // set the connection string name that you set up (caused issues before)
if (connectionString == null)
{
throw new ApplicationException("DefaultConnection is not set");
}
builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddDatasyncControllers();
var app = builder.Build();
// Initialize the database
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await context.InitializeDatabaseAsync().ConfigureAwait(false);
}
// Configure and run the web service.
app.MapControllers();
app.Run();
appDbcontext.cs:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
/// <summary>
/// The dataset for the UserItems.
/// </summary>
public DbSet<UserItem> UserItems => Set<UserItem>();
/// <summary>
/// Do any database initialization required.
/// </summary>
/// <returns>A task that completes when the database is initialized</returns>
public async Task InitializeDatabaseAsync()
{
await this.Database.EnsureCreatedAsync().ConfigureAwait(false);
}
}
my controller:
[Route("tables/useritem")]
public class tblUserController : TableController<UserItem>
{
public tblUserController(AppDbContext context)
: base(new EntityTableRepository<UserItem>(context))
{
}
}
and ofcourse the modeL.
public class UserItem : EntityTableData
{
[Required, MinLength(1)]
public string Email { get; set; } = "";
public string Telephone { get; set; } = "";
public string Password { get; set; } = "";
}
EDIT:
Also I have noticed that when I look at the link for my middleware, instead of getting a list of the items (which was the case with the todo items) I get this:
I have a class:
public class Application
{
....
public Deployment NewDeployment { get; set; }
....
}
I have an editor template for Deployment within the Application View folder.
The ApplicationViewModel has a SelectedApplication (of type Application), in my Index.cshtml where I use ApplicationViewModel as my Model, I have this call:
#using (Html.BeginForm("Create", "Deployment", new { #id = Model.SelectedId,
q = Model.Query }, FormMethod.Post, new { id = "form", role = "form" }))
{
#Html.EditorFor(m => m.SelectedApplication.NewDeployment)
}
Which then correctly renders out the control in my DisplayTemplates\Deployment.cshtml (though, it may just be pulling the display code and nothing in relation to the NewDeployment object's contents). All is well in the world until I go to submit. At this stage everything seems good. Controller looks like:
public class DeploymentController : Controller
{
[HttpPost]
public ActionResult Create(Deployment NewDeployment)
{
Deployment.CreateDeployment(NewDeployment);
return Redirect("/Application" + Request.Url.Query);
}
}
However, when it goes to DeploymentController -> Create, the object has nulls for values. If I move the NewDeployment object to ApplicationViewModel, it works fine with:
#Html.EditorFor(m => m.NewDeployment)
I looked at the output name/id which was basically SelectedApplication_NewDeployment, but unfortunately changing the Create signature to similar didn't improve the results. Is it possible to model bind to a child object and if so, how?
Your POST action should accept the same model your form is working with, i.e.:
[HttpPost]
public ActionResult Create(ApplicationViewModel model)
Then, you'll be able to get at the deployment the same way as you did in the view:
model.SelectedApplication.NewDeployment
It was technically an accident that using #Html.EditorFor(m => m.NewDeployment) worked. The only reason it did is because the action accepted a parameter named NewDeployment. If the parameter had been named anything else, like just deployment. It would have also failed.
Per Stephen Muecke's comment and with slight modifications, I was able to find how to correct it:
[HttpPost]
public ActionResult Create ([Bind(Prefix="SelectedApplication.NewDeployment")] Deployment deployment)
{
// do things
}
I have two ViewModels, the first has a list of type ProductViewModel, this list of type ProductViewModel is backed by a list of type Product in my model.
public List<ProductViewModel> Products
{
get
{
return (from product in ProductManager.Products
select new ProductViewModel(product)).ToList();
}
}
My first ViewModel will add products to the ProductManager.Products list and then raise a PropertyChanged notification so that the UI is updated. (So far so good).
A navigation to a second page then occurs, this then accesses the ProductManager.Products, once these products are processed, the ProductManager.Products list is cleared (by the second ViewModel).
Upon navigating back to the first view, how would I then update the List Products binding?
I am using the ViewModelLocator as provided by MVVMLight and therefore do not have static access to the first ViewModel from the second.
My current workaround is to create a BaseView page, override the OnNavigatedTo method, raise an event in this override, which I can then bind a Command to in my first ViewModel so that I can then call RaisePropertyChanged.
public class BaseView : PhoneApplicationPage
{
public event RoutedEventHandler NavigatedTo;
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (NavigatedTo != null)
{
NavigatedTo(this, new RoutedEventArgs());
}
}
}
<i:EventTrigger EventName="NavigatedTo">
<cmdextras:EventToCommand Command="{Binding Path=PerformNavigatedTo}" />
</i:EventTrigger>
public ICommand PerformNavigatedTo
{
get
{
return new RelayCommand(
() =>
{
RaisePropertyChanged(() => Products);
RaisePropertyChanged(() => SecondaryPageName);
},
() => true);
}
}
In addition to my workaround above, I previously investigated implementing messaging between ViewModels. Unfortunately this wasn't working for me as I had forgotten to include the Token when calling the Register method. As soon as I sorted this out I was able to send a message from the second ViewModel to be received by the first and therefore call the RaisePropertyChanged method as required.
Also, as soon as I realised that SimpleIoC returned the same instance after multiple calls to GetInstance, this helped in my understanding.
Castle Windsor passes the registered concrete type to Controller's constructors. A typical implementation (no pun intended) is:
private readonly IDepartmentRepository _deptsRepository;
public DepartmentsController(IDepartmentRepository deptsRepository)
{
if (deptsRepository == null)
{
throw new ArgumentNullException("deptsRepository");
}
_deptsRepository = deptsRepository;
}
I need to pass the ctor a second parameter, if possible, so that I can pass that val on to the Repository constructor (I know: tramp data alert, but I don't know if there's a straightforward way around it:
public DepartmentsController(IDepartmentRepository deptsRepository, int DBInstance)
{
if (deptsRepository == null)
{
throw new ArgumentNullException("deptsRepository");
}
_deptsRepository = deptsRepository(DBInstance);
}
REPOSITORY
public DepartmentRepository(int dbInst)
{
string connStr = string.Format("Phoo{0}Bar", dbInst);
using (var conn = new OleDbConnection(connStr))
{
using (var cmd = conn.CreateCommand())
{
. . .
Is it possible to tweak what Castle Windsor sends to the Controller constructor this way? If so, how?
AND/BUT: For this to be of any value (to me, anyway), I need to be able to get the int val (that will be passed to the Controller) from the URL the client sends. IOW, if the client asks the server for data via:
http://locohost:4242/Platypus/GetAll/1
I need to pass a "1" as the second argument to PlatypusController.
If the user asks the server for data via:
http://locohost:4242/Platypus/GetAll/42
I need to pass a "42" as the second argument to PlatypusController.
etc.
This is what I did to solve my Controller/Repository data context Dilemma:
0) Added a database context argument to the Controller's routing attribute. IOW, this:
[Route("api/HHSUsers/GetAll")]
...got changed to this:
[Route("api/HHSUsers/GetAll/{dbContext=03}")]
1) Passed that database context arg to the Repository. To wit, this:
return _hhsusersrepository.GetAll();
...got changed to this:
return _hhsusersrepository.GetAll(dbContext);
...so that the Controller method is now:
[Route("api/HHSUsers/GetAll/{dbContext=03}")]
public IEnumerable<HHSUsers> GetAllHHSUsersRecords(int dbContext)
{
return _hhsusersrepository.GetAll(dbContext);
}
2) Changed the corresponding method in the Repository interface from:
IEnumerable<HHSUsers> GetAll();
...to this:
IEnumerable<HHSUsers> GetAll(string dbContext);
3) Changed the Repository method from this:
public HHSUsersRepository()
{
// All the data is loaded here in the ctor
}
public IEnumerable<HHSUsers> GetAll()
{
return hhsusers;
}
....to this:
public IEnumerable<HHSUsers> GetAll(string dbContext)
{
LoadHHSUsers(dbContext);
return hhsusers;
}
private void LoadHHSUsers(int dbContext)
{
string connStr = string.Format("Foo{0}Bar", dbContext);
// The same as previously from this point on, except that this:
// using (var conn = new OleDbConnection(#"Foo Bar Phoo Bar etc"...
// becomes:
// using (var conn = new OleDbConnection(connStr))
4) Tack the dbcontext val to the end of the URL when calling the method, so that it is this:
http://localhost:28642/api/HHSUsers/GetAll/42
...instead of this:
http://localhost:28642/api/HHSUsers/GetAll
If the data context to use is "03" I can omit the dbcontext arg from the URL, as 03 is the default value I set when I appended "=03" to the Controller's "dbContext" routing attribute arg.
I know some fancy-pants propeller-heads will find fault with this for some reason (for one reason because of the tramp data going here and there and everywhere like a hobo on steroids), but my response is the same as that of an athlete who is getting trash-talked by an opposing player and yet whose team is winning: just point at the scoreboard. IOW, this works for me, so that's pretty much all I care about. Style points are for runway models and, again, fancy-pants propeller-heads (AKA Star-Bellied Sneeches (as opposed to us plain
cats with the unstarred bellies)); see "The perfect is the enemy of the good."
This simple way has that self-same benefit -- of being (relatively) simple to grok and, thus, modify/refactor as necessary. Inelegant? Sure, but so was Joe Kapp.
I need to work with data returned from a service which has a more complex JSON structure than the examples provided in the GXT documentation and thus far I cannot find any instructions or example which demonstrates how this might be accomplished.
The JSON contains multiple key/value pairs, but some of the key/value pairs are collections. I can have all of the data returned to me in one call from the service in the proper structure, but there does not appear to be a way to parse the data into separate entities. In my particular case I am attempting to configure a loader which will process one of the collections but I also need other key/value pairs from the same message (it is not ok to have the loader make one call and then have another call made for the same data and retrieve the other key/value pairs). Is there any way to accomplish this using GXT3?
Example: let's assume I can make a request from a server which returns JSON containing the name of an author along with a collection of the books the author has written. I want to display the author's name above a grid which lists the books. I want only one request made to the server and then have my view display the author in one component and the book list in a grid. Assume I need a loader instead of just a store as the grid may have to make additional calls (e.g. if it is a paging grid, livegrid, etc.).
Example JSON: (one JSON message returned with and author element along with a collection of book elements - I've indented the JSON to illustrate the structure)
{ "returnData" :
{"author" : "AuthorName"},
{"books" :
{"id" : "1", "name" : "Book1"},{"id" : "2", "name" : "Book2"}
}
}
Using the example for JsonReader (see the javadoc for an example) I can receive the request and parse the links into a collection using AutoBeans. This works fine when I need to have those retrieved and parsed in a loader. However, if I do that then the other properties are ignored. I currently don't see any way to parse the other values in the same request so they can be used elsewhere. My example code for the collection processing is below:
// this is the root JSON object, the AuthorRecord
public interface AuthorRecord {
#PropertyName(value="author")
String getAuthor();
#PropertyName(value="author")
void setAuthor(String author);
#PropertyName(value="books")
List<Book> getBooks();#
#PropertyName(value="books")
void setBooks (List<Book> books);
}
// models the book objects returned
public interface Book {
#PropertyName(value="id")
String getId();
#PropertyName(value="id")
void setId(String id);
#PropertyName(value="name")
String getName();
#PropertyName(value="name")
void setName(String name);
}
public interface ReturnData {
AuthorRootObject getAuthorRoot();
}
public interface LibraryAutoBeanFactory extends AutoBeanFactory {
AutoBean<ReturnData> authorRecord();
AutoBean<ListLoadConfig> loadConfig();
}
public class ReturnDataJsonReader extends JsonReader<ListLoadResult<Book>,
ReturnData> {
public ReturnDataJsonReader(AutoBeanFactory factory,
Class<ReturnData> rootBeanType) {
super(factory, rootBeanType);
}
#Override
protected ListLoadResultBean<Book> createReturnData(Object loadConfig,
ReturnData incomingData) {
return new ListLoadResultBean<Book>(incomingData.getBooks());
}
}
The problem I was having was that I need to have a view that includes a grid (paging grid, etc.) which lists out the books, while having the Author's name sit above the grid. I wanted to get all of this information (or at least the first page of results) with only one request to the server since the JSON message contains all the information I need to accomplish this. The problem is that the loader makes the request and receives the response, and it expects that the reader it will use is going to process a collection. In my case, I need the loader to process the collection of books but also populate another data field. The solution I found was to create an arbitrary collection to pass to the loader and then implement my own load handler to process the return object as needed.
1.The JSON being returned is really just one object of type ReturnData. The extended JsonReader could process this using AutoBeans, but if the reader is to be used for the loader, it needs to return a collection. Therefore, override the createReturnData() method to return a collection of one object.
public class ReturnDataJsonReader extends JsonReader<ListLoadResult<AuthorRecord>,
ReturnData> {
public ReturnDataJsonReader(AutoBeanFactory factory, Class<ReturnData> rootBeanType)
{
super(factory, rootBeanType);
}
#Override
protected ListLoadResultBean<AuthorRecord> createReturnData(Object loadConfig,
ReturnData incomingData) {
List<AuthorRecord> authorDataCollection = new ArrayList<AuthorRecord>();
authorDataCollection.add(incomingData);
return authorDataCollection;
}
}
2.The LoadHandler used in the examples takes a ListStore as an input and populates it with the results from the loader. Since the return object is not what we want populating the loader, and since we need to populate another property on the view, create your own LoadHandler to take the objects needed as input and populate them:
View Class Example:
public class ExampleViewClass {
// truncating most of the code in here for brevity
// note some of the objects referenced here reference objects in the question
private String authorName;
private ListStore<Book> bookList;
// IMPORTANT - create your own LoadHandler
private class LibraryLoadResultistStoreBinding<C, M, D extends
ListLoadResult<AuthorRecord>> implements LoadHandler<ListLoadConfig,
ListLoadResult<AuthorRecord>> {
private final ListStore<Book> bookStore;
private final String authorName;
public LibraryLoadResultistStoreBinding(ListStore<Book> books, String author) {
this.bookStore = books;
this.authorName = author;
}
#Override
public void onLoad(LoadEvent<ListLoadConfig, ListLoadResult<AuthorRecord> event)
{
// the response object
AuthorRecord response = event.getLoadResult().getData().get(0);
bookStore.replaceAll(response.getBooks());
author = response.getAuthor();
}
}
// example uses an HttpProxy but that's not required
public void populateView() {
LibraryAutoBeanFactory factory = GWT.create(LibraryAutoBeanFactory.class);
ReturnDataJsonReader reader = new ReturnDataJsonReader(factory, ReturnData.class);
String path = "http://path.to.resource/getinfo";
RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, path);
HttpProxy<ListLoadConfig> proxy = new HttpProxy<ListLoadConfig>(builder);
final ListLoader<ListLoadConfig, ListLoadResult<AuthorRecord>> loader = new
ListLoader<ListLoadConfig, ListLoadResult<AuthorRecord>> (proxy, reader);
loader.useLoadConfig(ReturnDataAutoBeanFactory.instance.loadConfig().as();
loader.addLoadHandler(new LibraryLoadResultistStoreBinding<ListLoadConfig,
AuthorRecord, ListLoadResult<AuthorRecord>>(bookList, authorName);
// pass in the objects to be populated
loader.load(); // fire the loader
}