Windows Phone - MVVM Light - Multiple ViewModel in a single page - c#

I am new to Windows Phone development.
I am writing an application for Windows Phone 8 using MVVM Light Toolkit.
I have a MainPage with longlistselector navigating to the details page with the relaycommand and everything is good.
Now in the detail page I have to fill out the UI controls with the binding context received from the MailPage (selecteditem of the longlistselector). My problem is that I have in the detail page and which selecteditem should be bound to the data context received from the mainpage.
Just to give an example in the mainpage I have the lostlingselector bound to a list of task objects of the mainviewmodel; every task have its own category which could be selected from the availabe task categories. How could I approach this? Is it possible to bound the ItemSource of the ListPicker control in the detail page to a different viewmodel and the SelectedItem of the same control to the proprties Category of the default viewmodel (selected task object)?
Thank you.

You can create a new view with it's own viewmodel and pass the data between viewmodels using the MVVM Light Messenger class.
Something like:
public class DetailsViewModel
{
public DetailsViewModel()
{
Messenger.Default.Register<Item>(this, "ItemDetails", i=> ViewItemDetails(i));
}
public void ViewItemDetails(Item i)
{
//Now you can bind it to your UI
}
}
And pass the object from your main viewmodel just like this (ItemDetails it's just a token to identify the listeners)
Messenger.Default.Send<Item>(SelectedItem, "ItemDetails");

You shouldn't really mess up with bindings in between different ViewModel. To setup interaction between viewmodel's you can use Messnger from MvvmLight toolkit, inject it in both objects and define proper pub-sub relations.
public class FirstViewModel : INotifyPropertyChanged
{
private IMesseger messenger;
...
public FirstViewModel(IMessenger messenger)
{
this.messenger = messenger;
}
public Item SelectedItem
{
get
{
return this.selectedItem;
}
set
{
this.selectedItem = value;
this.messenger.Send(new GenericMessage<Item>(this.selectedItem));
this.OnPropertyChanged("SelectedItem");
}
}
}
public class SecondViewModel : INotifyPropertyChanged
{
private IMesseger messenger;
...
public SecondViewModel(IMessenger messenger)
{
this.messenger = messeger;
this.messenger.Register<GenericMessage<Item>>(this, this.HandleItemSelected);
}
...
}
So after sharing same instance of messenger between two VM's you'll have desired functionality with loosely coupled relations which is good from testing perspectives.

Related

How to share a binding value between multiple views?

I am new to WPF and MVVM (coming in from WinForms and Events), so please bear with me!
I am trying to figure out how to use the same INotifyPropertyChanged value binding between multiple views. I am using MVVM Light. I have ViewModels that inherit from ViewModelBase backing my Views (with no code behind). I'm not sure how to explain the issue, but I think an example will make it clear what I'm after.
I have a main window. It has a standard TabControl. On the Login TabItem I have a custom login control. Below the TabControl, I have a custom status bar control. The desired behavior is that when the user logs in, the status bar is updated with their login status and name, and the other TabItems on the main window become enabled (they should be disabled when not logged in).
So to summarize I have:
MainWindow (view) with MainWindowViewModel
Login (view) with LoginViewModel (in TabControl of MainWindow)
StatusBar (view) with StatusBarViewModel (at bottom of MainWindow)
Here is what my StatusBarViewModel looks like:
public class StatusBarViewModel : ViewModelBase, IStatusBarViewModel
{
private bool _isLoggedIn;
public bool IsLoggedIn
{
get { return _isLoggedIn; }
set { Set(ref _isLoggedIn, value); RaisePropertyChanged(); }
}
// other properties follow
}
I inject (using Ninject) the (singleton) concrete instance of IStatusBarViewModel into the LoginViewModel via constructor injection:
public class LoginViewModel : ViewModelBase
{
private IStatusBarViewModel _statusBar;
public LoginViewModel(IStatusBarViewModel statusBar)
{
_statusBar = statusBar;
}
}
And I do the same for the MainWindowViewModel:
public class MainWindowViewModel : ViewModelBase
{
private IStatusBarViewModel _statusBar;
public bool IsLoggedIn => _statusBar.IsLoggedIn;
public MainWindowViewModel(IStatusBarViewModel statusBar)
{
_statusBar = statusBar;
}
}
Note: I think this is where my problem is... Not sure if MVVM Light interprets this as a bindable property and applies the proper change notifications. If I add a setter (which I don't need here), that won't work because A property or indexer may not be passed as an out or ref parameter. So I'm unclear on what is going on when I do this.
Back on track here, so when the login is successful, I am able to update the IsLoggedIn property from the LoginViewModel like so:
_statusBar.IsLoggedIn = true;
I set up the binding in my MainWindow XAML like so:
<TabItem Header="Event" IsEnabled="{Binding IsLoggedIn}">
<views:Events/>
</TabItem>
The binding works correctly when the view is first loaded, but subsequent changes to the property don't trigger a change in IsEnabled. The StatusBar (view) however does update accordingly.
I had tossed around the idea of injecting references to both the StatusBarViewModel and the MainWindowViewModel in to my LoginViewModel (and then having to set two properties after login), but that made me think that I'm not approaching this correctly because I'm creating dependencies.
So basically the question is:
Is my approach correct, per the MVVM pattern?
Am I on the right track and just need to modify the code a bit?
If not, what is the (or a) standard pattern to handle this scenario?
Your guess is correct. The problem is here:
public bool IsLoggedIn => _statusBar.IsLoggedIn;
... because it's not going to generate the change notification. What you could do is just expose the IStatusBarViewModel via a public property and then bind to its own IsLoggedIn property directly.
In the ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private IStatusBarViewModel _statusBar;
public IStatusBarViewModel StatusBar => _statusBar;
public MainWindowViewModel(IStatusBarViewModel statusBar)
{
_statusBar = statusBar;
}
}
And in the View:
<TabItem Header="Event" IsEnabled="{Binding StatusBar.IsLoggedIn}">
<views:Events/>
</TabItem>

How to open a modal dialog (view) from a ViewModel (in mvvm)

I have an application which holds objects in a list view. I want to enable user to change those objects using modal dialogs when an item is double clicked in the list view.
I am using the mvvm light toolkit. When I double click an item in the list view I know which object and therefor which model type is chosen. I am retrieving the corresponding ViewModel via a ServiceLocator and I am using Execute to "launch" the viewmodels' relay command passing the model as object with the needed data information. However, now, in the ViewModel, I am struggling how to open the corresponding view as a model dialog bound to the ViewModel?
Edit (add some code fragments)
public class ViewModelLocator
{
public ViewModelLocator
{
SimpleIoc.Default.Register<OptionSpecificViewModel>();
}
public OptionSpecificViewModel OptionSpecificView
{
get
{
return ServiceLocator.Current.GetInstance<OptionSpecificViewModel>();
}
}
}
the view locator works fine
public class MyListViewManager
{
public void CallMyDialog(Guid xxx)
{
var objModel = GetMyModelByGuid(xxx);
var vm = CommonServiceLocator.ServiceLocator.Current.GetInstance<ObjectSpecificViewModel>();
vm.EditCommand.Execute(objModel);
}
}
the "ListViewManager" works also
My Problem is, that I am in the correct ViewModel with the correct model (data).
public class OptionSpecificViewModel : ViewModelBase
{
public OptionSpecificViewModel()
{
InitRelayCommands();
RegisterMessages();
}
...
public void OnEditCommand(object model)
{
// I reach here in the correct view model with the correct model
// but how can I open the view here??
}
}
As far as I understood MVVM, the view model does know the model and the view has a "connection" to the ViewModel via binding.
But the ViewModel does not know the view. so how do I start the view?

Implementing interfaces in View and ViewModels using MEF and MVVM

Background : I have a Window having a tab control with each tab having a separate UserControl. I have followed MVVM for each user control and MEF to obtain the controls to be displayed in the tab at runtime. This is my implementation
interface ITabControl
{
}
[Export(typeof(UserControl1ViewModel))]
class UserControl1ViewModel
{
}
class UserControl1: ITabControl
{
[Import(typeof(UserControl1ViewModel))]
public UserControl1ViewModel ViewModel
{
get { return this.DataContext as UserControl1ViewModel; }
set { this.DataContext = value; }
}
}
//Other user controls have similar implementation
public class WindowViewModel
{
//Import all type of ITabControl and set the TabCollection(bound to ItemSource property of tab control)
}
Problem : Now I have some validations to be done on a particular set of tabs based on the user action in the main window. So I have used another interface called IConfiguration which is implemented by some user control ViewModels.
interface IConfiguration
{
public void Action1();
public void Action2();
------------------- (many more)
}
public class Window
{
//Import all type of IConfiguration and call Action1/Action2 for all these types based on user actions.
}
Now, if an error is encountered during validation (IConfigure actions impelemented in different ViewModels) in any of the above tabs, I need to set the SelectedTabItem property of the tab control to that particular tab. Since these actions are implemeted in the ViewModel, I'm unable to obtain the UserControl to set the SelectedTabItem property. How do I achieve this?
PS: I know I can achieve this by implementing IConfiguration in UserControl view instead of ViewModel this way
public class UserControl1 : IConfiguration
{
public void Action1
{
this.ViewModel.Action1();
}
public void Action2
{
this.ViewModel.Action2();
}
//--------
}
I wonder if there is a better way to achieve this.
Use an overarching viewmodel which contains a collection of ViewModels (one per tab) and a property which represents the active tab.
When you need to swap the active tab you can do it in the viewmodel just by updating the property that represents the active tab. This answer here shows you how to bind the active tab in the TabControl.

WPF - MVVM Light, Ribbon Control, nested user controls/views and using events between user controls/views

I am building a WPF application with the aid of MVVM light and Unity.
I have a ribbon control in the main form, with one tab without tab category, and two tabs in a tab category. User control 3 includes user control 2 and user control 1.
I would like to make visible the Tab Category and tabs 2 and 3 when I click in user control 2 and make them invisible when click in user control 3 or 1.
I confused a little bit with the event handling in mvvm structure.
I would like to do this properly with the MVVM rules. Could you give me an example or some really helpful directions how to do it?
Check how it looks like
Your Ribbon or tabs for that matter, should be backed up by a ViewModel, let's say TabCategoryViewModel or Tab2ViewModel and Tab3ViewModel. Inside these ViewModels you inject the IMessanger service (of course register it before if not already done) and create a POCO event message, like SelectedViewMessage
public class SelectedViewMessage
{
public string ViewName { get; set; }
}
Inside your TabCategoryViewModel you would register to listen to this message
public class TabCategoryViewModel : ViewModelBase
{
public readonly IMessanger messageService;
public TabCategoryViewModel(IMessanger messageService)
{
if(messageService == null)
{
throw ArgumentNullException("messageService");
}
this.messageService = messageService;
this.messageService.Register<GoToPageMessage>(this, OnSelectedViewChanged);
}
protected void OnSelectedViewChanged(SelectedViewMessage message)
{
this.IsVisible = message.ViewName == "UserControl2";
}
private bool isVisible;
public bool IsVisible
{
get { return isVisible; }
set
{
if(isVisible != value)
{
isVisible = value;
RaisePropertyChanged();
}
}
}
}
You inject the same IMessanger service into your ViewModel you use to Bind your UserControl2 and fire the message via
var message = new SelectedViewMessage {
ViewName = "UserControl2";
};
this.messangerService.Send<SelectedViewMessage>(message);
This code can be placed inside a ViewSelectedCommand or something similar, and you can use Blend Interactivity Triggers/Actions to bind this to certain events on the View/UserControl
This can be achieved by adding event onPropertyChange whenever user clicks on the tab and add that property in xaml under Visibility tag. Also look at Handling UI Control to understand that mapping in xaml and ti understand event handling from Here
Hope it helps.

how to refresh ObservableCollection in View1 when coming back from view2 in wp8 mvvm project

I have an MVVM project in which the main view ("View1") has a button called Save and listbox control which has binding of model emp.
The viewmodel (ViewModel1) implements inotifypropertychanged and it has SavedButtonCommand binding to Save button of View1 and saves the record to SavedEMp.In View1 there is a button "Next" which takes to the "View2" page.View2 page has ViewModel2 which has SaveAgain button .And viewmodel2 has a binding to the SaveAgainButtonCommand and it also saves the record to the localdb called SavedEmp of emp records.
View1 listbox gets refreshed when the statement RaisePropertyChanged("SavedEmpDataSoruce") gets executed and the observablecollection "SavedEmpDataSoruce" is binding to this listbox.Since these two are in View1 after the save button whenenever the statement RaisePropertyChanged("SavedEmpDataSoruce") gets executed it is refreshing the data.
But when user comes back from View2 to View1 the listbox in View1 is not refreshed with the data of View2 saved emp record.
private ObservableCollection<Emp> _SavedEmp;
public ObservableCollection<Emp> SavedEmp
{
get
{
if(_SavedEmp == null)
{
_SavedEmp = Emp.GetSavedEmps();
}
return _SavedEmp;
}
set
{
this._SavedEmp = value;
RaisePropertyChanged("SavedEmp");
}
}
Is there a way we can refresh the listbox data when moved from View2 to View1 again ?
Communication between dependent views can be accomplished using a Messenger-style object. An implementation of this object comes standard with the MVVM Light toolkit if you are making use of it. The pattern consists of one object registering with the messenger to receive messages of a given type at a provided callback, and another object dispatching a message of that type. The messenger maintains the lists of addressees for these messages and delivers them accordingly as parameters to the callback function provided by the recipient. A sample implementation using MVVM Light's messenger follows:
// Message container
public class AccountChangedMessage : GalaSoft.MvvmLight.Messaging.GenericMessage<Account>
{
public AccountChangedMessage(Account a) : base(a) { }
}
// Dependent ViewModel
public class AccountsViewModel : GalaSoft.MvvmLight.ViewModelBase
{
public AccountsViewModel()
{
MessengerInstance.Register<AccountChangedMessage>(this, OnAccountChanged);
}
private void OnAccountChanged(AccountChangedMessage msg)
{
// TODO: Rebuild bound data
}
}
// Initiating ViewModel
public class AccountEditViewModel : GalaSoft.MvvmLight.ViewModelBase
{
public void SaveAccount()
{
_accountService.Save(_account);
MessengerInstance.Send(new AccountChangedMessage(_account));
}
}
In a sense, this is akin to raising events and registering listeners for those events, but the model is very powerful and much more disconnected, so please use it as sparingly as possible, as this does create a maintenance concern for programmers who come behind you and attempt to follow control flow.

Categories