Pass data to parent view--communication between views - c#

I have created a view which has a GridView and a Button. I call it a parent view. At the beginning, the ItemsSource collection of GridView is empty. Now I click the button to pop up a modal/popup view I call it as a Child view.
In the child view there are a couple buttons. One of them is to create new record for the parent view.
For example, if I click it, a new row data is generated.
Now my question is how to send the data just created back to the parent view so that the GridView in the parent view can be refreshed?

You can use IEventAggregator service to communicate between the two views Create an event, subscribe to it in the parent view or the parent view ViewModel, and publish it in the child view or the child view ViewModel. This way, there is no explicit dependency between the two views.
Your record model class:
public class RecordModel
{
//Some Properties
}
Declare the event:
public class RecordAdded : PubSubEvent<RecordModel>
{
}
In the parent view / viewModel:
eventAggregator.GetEvent<RecordAdded>().Subscribe(OnRecordAdded);
private void OnRecordAdded(RecordModel e)
{
}
In your child view / viewmodel:
eventAggregator.GetEvent<RecordAdded>().Publish(new RecordModel
{
//The popup data
});

Your view models will be doing all the work, I suppose. If so, you have a lot of options for communication between them, the most obvious is through IEventAggregator. Alternatively, create a service as data source for the parent view, and give it an iterface to the child view that exposes the CreateItem functionality... for the service to notify the parent view of the new item, you can go for ObservableCollection, INotifyPropertyChanged, IEventAggregator or simple .Net events, just to name a few examples.
IEventAggregator example:
internal class ChildViewModel
{
public ChildViewModel( IEventAggregator eventAggregator )
{
CreateItemCommand = new DelegateCommand( () => eventAggregator.GetEvent<ItemCreatedEvent>().Publish( new Item() ) );
}
}
internal class ParentViewModel
{
public ParentViewModel( IEventAggregator eventAggregator )
{
eventAggregator.GetEvent<ItemCreatedEvent>().Subscribe( x => UpdateData( x ) );
}
}

Related

c# WPF MVVM TabControl with Multiple ViewModels and changing tabs

So I currently have a Window with a TabControl. The MainWindow has its own ViewModel and all the TabItems have their own ViewModels also.
I can easily change tabs from the MainWindow ViewModel through a bound SelectedIndex property. What I would like to do is change to another tab from code that runs within ANOTHER tab viewmodel. Since the Tabs are not part of the MainWindowViewModel, I am looking for a clean way to change the Tab without resorting to code behind to do it.
There are also cases, where I might need to change the tab from something such as a message prompt. I thinking my only way is to create and event and subscribe to that from MainWindowViewModel.
So I solved this with an EventAggregator.
public static class IntAggregator
{
public static void Transmit(int data)
{
if (OnDataTransmitted != null)
{
OnDataTransmitted(data);
}
}
public static Action<int> OnDataTransmitted;
}
First ViewModel sends data.
public class ModifyUsersViewModel : INotifyPropertyChanged
{
private void change_tab(int data)
{
IntAggregator.Transmit(data);
}
}
Second ViewModel receives data and then does something with it.
public class MainWindowViewModel : INotifyPropertyChanged
{
private int _Tab_SelectedIndex = 0;
public int Tab_SelectedIndex
{
get
{
return _Tab_SelectedIndex;
}
set
{
_Tab_SelectedIndex = value;
OnPropertyChanged(new PropertyChangedEventArgs("Tab_SelectedIndex"));
}
}
public MainWindowViewModel()
{
IntAggregator.OnDataTransmitted += OnDataReceived;
}
private void OnDataReceived(int data)
{
Tab_SelectedIndex = data;
}
}
Rather than trying to bind to SelectedIndex, if the TabItems have their own view models, then you can create a property for each of those view models: IsSelected and then bind the TabItem.IsSelected property to that:
<TabItem IsSelected="{Binding IsSelected}">
This prevents the view models from needing to know the index of their corresponding TabItem, something I would argue is a detail that should be specific to the view and something the view model should not concern itself with. What if you add another TabItem or want to change the order? Now you've got changes to make in the view models for something that could be just simple change to the view.

DataContext of view is always null

I'm trying to access the view model of a view in code-behind using the DataContext property. However, no matter at what point in the view lifecycle I'm trying to access it, the property is always null.
Simple dummy setup:
// shell
internal class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
public ShellViewModel() {
ActivateItem(new MyTestViewModel());
}
}
// view code-behind
public partial class MyTestView : UserControl {
public MyTestView() {
InitializeComponent();
}
protected override void OnInitialized(EventArgs args) {
var vm = DataContext as MyTestViewModel;
Debug.Assert(vm != null); // is always null!
}
}
The view get's correctly initialized, the view model is being called and both can be bound together. When I bind a property of the view model to a view control, it can be accessed without problems. Only during initialization, the DataContext of the view is always null. What do?
MVVM framework: Caliburn.Micro
public partial class MyTestView : UserControl {
public MyTestView() {
InitializeComponent();
}
public MyTestViewModel ViewModel() {
return (MyTestViewModel)Datacontext;
}
}
Doing this for whatever reason sort of breaks the idea behind the pattern since the viewmodel shouldn't be hardcoded into the view. Understandable for testing purposes but for any actual use; bad form.
--Edit -- I was thinking view only actions, but for some reason it was late and wasn't thinking about accessing data from within the view not accessing view from within ViewModel.
This should get what you need. Only after the Framework binds the view with the viewmodel (i.e. datacontext is set, once Loaded has been hit) will this work. If you need to do something beforehand I am not sure what else would be a better solution.

Advice on Views navigation using Caliburn.Micro MVVM WPF

I'm new on Caliburn Micro and want some advice on which path to take to devolop my app interface and navigation between views.
My idea is to have a MainWindow which will contain a menu of buttons, each one related with a specific view. Each view will be stored in a separated WPF UserControl. The mainWindow will also contain a TabControl bound to an ObservableCollection of tabs on viewmodel. Everytime a button on menu is clicked, I want to add a new tab with a ContentPresenter inside that will dynamically load a view and its corresponding viewmodel.
So my questions:
1) Should I use a Screen Collection here?
2) Should the UserControl implement Screen interface?
3) How do I tell MainWindow ViewModel which view to load on the new added tab maintaining viewmodels decoupled?
Thanks to everyone in advance.
UPDATE
After a lot of reading and some help of the community I managed to resolve this. This is the resultant AppViewModel:
class AppViewModel : Conductor<IScreen>.Collection.OneActive
{
public void OpenTab(Type TipoVista)
{
bool bFound = false;
Screen myScreen = (Screen)Activator.CreateInstance(TipoVista as Type);
myScreen.DisplayName = myScreen.ToString();
foreach(Screen miItem in Items)
{
if (miItem.ToString() == myScreen.ToString())
{
bFound = true;
ActivateItem(miItem);
}
}
if (!bFound) ActivateItem(myScreen);
}
public ObservableCollection<MenuItem> myMenu { get; set; }
public ObservableCollection<LinksItem> myDirectLinks { get; set; }
public ICommand OpenTabCommand
{
get
{
return new RelayCommand(param => this.OpenTab((Type) param), null);
}
}
public AppViewModel()
{
OpenTab(typeof(ClientsViewModel));
MenuModel menu = new MenuModel();
myMenu = menu.getMenu();
myDirectLinks = menu.getLinks();
}
public void CloseTab(Screen param)
{
DeactivateItem(param, true);
}
}
I have to keep the ICommand from OpenTabCommand because the name convention of Caliburn.micro doesn't seems to work inside DataTemplate. Hope it could help someone else. Thanks to all
I've done something very similar using Caliburn.Micro, and based it on the SimpleMDI example included with the examples, with a few tweaks to fit my needs.
Much like in the example, I had a main ShellViewModel:
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
}
with a corresponding ShellView containing a TabControl - <TabControl x:Name="Items">, binding it to the Items property of the the Conductor.
In this particular case, I also had a ContextMenu on my ShellView, bound (using the Caliburn.Micro conventions), to a series of commands which instantiated and Activated various other ViewModels (usually with a corresponding UserControl, using the ActivateItem method on the Conductor.
public class YourViewModel: Conductor<IScreen>.Collection.OneActive
{
// ...
public void OpenItemBrowser()
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
In that case, I didn't require the ViewModels to be created with any particular dependency, or from any other locations in the program.
At other times, when I've needed to trigger ViewModel from elsewhere in the application, I've used the Caliburn.Micro EventAggregator to publish custom events (e.g. OpenNewBrowser), which can be handled by classes implementing the corresponding interface (e.g. IHandle<OpenNewBrowser>), so your main ViewModel could have a simple Handle method responsible for opening the required View:
public class YourViewModel: Conductor<IScreen>.Collection.OneActive, IHandle<OpenNewBrowser>
{
// ...
public void Handle(OpenNewBrowser myEvent)
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
This section of the documentation will probably be useful, especially the Simple MDI section.
Additional code I mentioned in the comments:
I sometimes use a generic method along these lines ensure that if I have an existing instance of a screen of a particular type, switch to it, or create a new instance if not.
public void ActivateOrOpen<T>() where T : Screen
{
var currentItem = this.Items.FirstOrDefault(x => x.GetType() == typeof(T));
if (currentItem != null)
{
ActivateItem(currentItem);
}
else
{
ActivateItem(Activator.CreateInstance<T>());
}
}
Used like:
public void OpenBrowser()
{
this.ActivateOrOpen<BrowserViewModel>();
}

Saving data from multiple tabs in a tab control

I've been tasked with making a few changes to an existing program.
One of those changes is implementing a 'save' button.
When clicked, it will iterate through each tab and save the contents to a database, but I can't figure out how to access the data properly.
The tabs being added are separate views, each with their own viewmodel - the main view containing the tabcontrol also has its own viewmodel.
How would I go about accessing the tabcontrol, iterating through each tab and saving the data in an orderly fashion?
(At this point I'm not sure if it's relevant to show any code, but please do request whatever you'd need)
Assuming, that every tab supports changes saving, create a view model, that will be on top of tab view models:
// this is the base class for tab view models
public class DocumentViewModel
{
public void SaveChanges() {}
}
// this is the view model for tab container
public class EditorViewModel
{
private SaveChanges()
{
foreach (var document in OpenedDocuments)
{
document.SaveChanges();
}
}
public EditorViewModel()
{
SaveCommand = new RelayCommand(SaveChanges);
}
// this is your tabs
public ObservableCollection<DocumentViewModel> OpenedDocuments { get; private set; }
public ICommand SaveChangesCommand { get; private set; }
}
If i understood correctly you have all data accessible in your viewmodel, there is no need to iterate through the tabs in the tabcontrol.
That 'save' button should bind to a command that collects data from each tabs viewmodel.

MVVM: Which component is in charge of navigation?

I am working on a Windows Phone 7 application. Now I need to switch the view after a user tapped the designated button which takes user to another view.
Which component, theoretically, in MVVM should be in charge of the navigation, i.e. switching views? Code snippets would be good to show demonstration.
I have tried inserting the switching code in View and it works alright, but I encountered a situation where I call an asynchronous web service and would like to navigate user to the new view only after the operation is done, the navigation code should be inside the event handler.
Thank you.
P/S: My project's deadline is coming soon, I have no time to rebuild my project using MVVM tools, such as MVVM Light, Caliburn Micro, and etc.
I put a Navigate methods in the base class that all my ViewModel's share:
protected void Navigate(string address)
{
if (string.IsNullOrEmpty(address))
return;
Uri uri = new Uri(address, UriKind.Relative);
Debug.Assert(App.Current.RootVisual is PhoneApplicationFrame);
BeginInvoke(() =>
((PhoneApplicationFrame)App.Current.RootVisual).Navigate(uri));
}
protected void Navigate(string page, AppViewModel vm)
{
// this little bit adds the viewmodel to a static dictionary
// and then a reference to the key to the new page so that pages can
// be bound to arbitrary viewmodels based on runtime logic
string key = vm.GetHashCode().ToString();
ViewModelLocator.ViewModels[key] = vm;
Navigate(string.Format("{0}?vm={1}", page, key));
}
protected void GoBack()
{
var frame = (PhoneApplicationFrame)App.Current.RootVisual;
if (frame.CanGoBack)
frame.GoBack();
}
So the ViewModel base class executes the navigation if that's what you are asking. And then typically some derived ViewModel class controls the target of the navigation in response to the execution of an ICommand bound to a button or hyperlink in the View.
protected SelectableItemViewModel(T item)
{
Item = item;
SelectItemCommand = new RelayCommand(SelectItem);
}
public T Item { get; private set; }
public RelayCommand SelectItemCommand { get; private set; }
protected override void SelectItem()
{
base.SelectItem();
Navigate(Item.DetailPageName, Item);
}
So the View only knows when a navigate action is needed and the ViewModels know where to go (based on ViewModel and Model state) and how to get there.
The view should have a limited number of possible destinations. If you have to have a top-level navigation on every page, that should be part of your layout or you can put them in a child view.
I put navigation outside of MVVM in a class that is responsible for showing/hiding views.
The ViewModels use a messagebroker with weakevents to publish messages to this class.
This setup gives me most freedom and doesn't put any responsibilities in the MVVM classes that do not belong there.

Categories

Resources