Caliburn how to Process Action from Outer View in the inner View - c#

I have a very frustratig problem:
I have a outer View, which has a Ribbonbar at the top. There is another View, which holds 1...n Viewmodels and displays a datagrid from a List of Datatables. Which one is shown, is up to the user.
The User can select a Button on the Ribbonbar, which should issue an operation on the selected rows in the Datagrid. But how to do this?
I could easily launch a method on the active ViewModel, but the method then needs to get hold of all selected rows - which would violate the Idea behind MVVM.
Any Ideas?
Here is a look of the Screen:
http://s7.directupload.net/file/d/3228/a3m3ttu9_jpg.htm
The Button "Zeile raus", should cause an Effect in the ViewModel / Viwe contained in the lower right Tabcontrol. The Effect needs to know which rows are selected.

Have the button publish an event from its command execute method :
public class RibbonViewModel {
IEventAggregator events;
public RibbonViewModel (IEventAggregator events){
this.events = events;
}
public void ButtonClickCommandExecute(){
events.Publish(new SomeMessage{
SomeNumber = 5,
SomeString = "Blah..."
});
}
}
Each of your ViewModel should subscribe to this event, and react on it if it is the "active" ViewModel :
public class ViewModelWithDataGrid : IHandle<SomeMessage>{
public void Handle(SomeMessage message){
if(IsActive){
//do something with the message
}
}
}
This way event source is not coupled to event sink, and you can easily unit test whenever a VM should respond to an event.
Documentation : http://caliburnmicro.codeplex.com/wikipage?title=The%20Event%20Aggregator&referringTitle=Documentation

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.

Pass data to parent view--communication between views

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 ) );
}
}

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.

Triggering RelayCommand from code-behind

I have a Main Page for my application that is formed froma number of other windows. One of which is the settings for my applications and is open/closed by clicking a button from my Main Page. This window has a View Model as well as two buttons, Save and Cancel. Pressing Cancel is used to restore the previous settings and Save just stores them. Now, when I use the main menu to close the Properties I want the Cancel to be called and am wondering about the best way to do this.
So in my view model I have something like this:
public RelayCommand CancelRC { get; private set; }
public PropertiesViewModel
{
CancelRC = new RelayCommand(RestoreProperties)
}
private RestoreProperties
{
// Restore
}
In my MainPage.xaml.cs
private void Properties_Click(object sender, RoutedEventArgs e)
{
if (PropertiesForm.Visibility == Visibility.Collapsed)
{
PropertiesForm.Visibility = Visibility.Visible;
PropertiesForm.IsSelected = true;
}
else
{
PropertiesForm.Visibility = Visibility.Collapsed;
IncidentForm.IsSelected = true;
// Send a Cancel relaycommand to the Properties View Model?
}
}
The obvious solution to me is to trigger the RelayCommand manually but I am not sure this is the most appropriate way to do it and I am not sure how you would trigger that anyway. So it this the way to do it or is there a more preferred way to do something like this?
Similarly is manually hiding and showing the Properties via the Main Page like this the best way to do it?
Remove the Properties_Click method and in your xaml do the following:
<Button Command="{Binding CancelRC}">Properties</Button>
This will cause the button to use RelayCommand.CanExecute and RelayCommand.Execute with no code on your part. The code assumes the window datacontext is set to your View Model.

Categories

Resources