WPF Adding tab items to tab control from multiple view models - c#

On my MainWindow I have a TabControl whose ItemSource is bound to an ObservableCollection<TabItem> In it's View Model (vmMainWindow).
On the Main Window I also have a Menu with 2 MenuItems, (Albums, Artists)
I have created a Page and ViewModel for both Albums and Artists.
When a MenuItem is clicked, using a RelayCommand I am creating a Frame to hold the relevent Page bound to it's respective View Model. Then i create a new TabItem set it's Content to the new Frame then add the new TabItem to the ObservableCollection<TabItem>.
void MenuItemClick(object parameter)
{
switch (parameter)
{
case "Albums":
Frame albumsFrame = new Frame { Content = new pgAlbums { DataContext = new vmAlbums() } };
TabCollection.Add(new TabItem { Header = "Albums", Content = albumsFrame , IsSelected = true });
break;
case "Artists":
Frame artistsFrame = new Frame { Content = new pgArtists { DataContext = new vmArtists() } };
TabCollection.Add(new TabItem { Header = "Artists", Content = artistsFrame , IsSelected = true });
break;
}
}
What i would like to do is to be able to add tabs to the ObservableCollection<TabItem> the same way from the other Page's View Models but I don't have access to the TabCollection from them. I either need somewhere global to place it instead or somehow share it between View Models.
I am aware I am going about this all the wrong way so i'm putting it out here so someone can guide me in the right direction about what to do. I'm a complete newbie to MVVM, i have only experience in Winforms, but want to move on from that.

You could use an EventAggregator or a Messenger class to send a message that the other view model subscribes to. Please refer to the following links for more information about the concept.
https://msdn.microsoft.com/en-us/magazine/jj694937.aspx
http://dotnetpattern.com/mvvm-light-messenger
https://blog.magnusmontin.net/2014/02/28/using-the-event-aggregator-pattern-to-communicate-between-view-models/
The other options would be to either inject both view models with a shared service or keep a strong reference to the first view model from the second one and raise an event.
The benefit of using an event aggregator or a messenger is that you avoid introducing a tight coupling between publisher of the event and the subscriber which should make the application easier to maintain. Both the publisher and the subscriber of the event or message knows only about the event aggregator or messenger but they don't know anything about each other.
By the way, you shouldn't create TabItems or any other visual elements in a view model. You should create instances of data objects that may be represented by visual elements such as TabItems in the view.

Related

In a Xamarin Master-Detail MVVM setup, how to fire off an event from a Child View's View Model and have it caught by the Parent View's View Model

TLDR; Since Xamarin Forms doesn't support Routed Events, is there an alternative pattern in Xamarin Forms to do what I'm asking here. What I need to do is fire off an event from a Child View's View Model and have it caught by the Parent View's View Model.
In my setup, I have a page Root Page that is a MasterDetail page:
public partial class RootPage : MasterDetailPage
I set the master to MainMenuPage and the child to MyChildPage1.
var rootPage = new RootPage
{
Master = new MainMenuPage(),
Detail = new MyChildPage1()
};
App.MainPage = rootPage;
I assign a ViewModel to all three of them:
rootPage.BindingContext = new RootPageViewModel();
rootPage.Master.BindingContext = new MainMenuPageViewModel();
rootPage.Detail.BindingContext = new MyChildPage1ViewModel();
There's a button in MainMenuPage. When you click it, it fires an ICommand on MainMenuPageViewModel. This is the normal MVVM / Binding stuff.
In MainMenuPageViewModel in the ICommand, I want to fire off something. (Routed Event?) And I want this event caught by RootPage. RootPage will then change a property on the ViewModel bound to rootPage.Detail (how?)
You can use MessagingCenter
Xamarin Messaging Center

Prism RequestNavigate and ViewModel OnPropertyChanged

I am using the Telerik RadRibbonView in my WPF 4.5 project. The set up looks like this.
In my Shell I have a RibbonView and a TabControl defined as a regions called “RibbonRegion” and “TabRegion”. The RibbonRegion is basically the menu of the application and the TabRegion holds the main content.
I have also created a module with a View containing a RibbonTab and a RibbonButton. This button is hocked up to a command that sets the DataContext of a RibbonContextTabView and a TabItemView and registers them in their respective regions. The ContextTab and the TabItem is sharing the same ViewModel. This ViewModel has a propery “IsSelected” that the ContextTab and TabItem are bound to.
if (_regionManager.Regions["RibbonRegion"].Views.Any(v => v.GetType() == typeof(ContextTabView)) && _regionManager.Regions["TabRegion"].Views.Any(v => v.GetType == typeof(TabItemView)))
{
_regionManager.RequestNavigate("RibbonRegion", new Uri("ContextTabView", UriKind.Relative));
_regionManager.RequestNavigate("TabRegion", new Uri("TabItemView", UriKind.Relative));
}
else
{
ContextTabView contextTabView = _container.Resolve<ContextTabView>();
TabItemView tabItemView = _container.Resolve<TabItemView>();
contextTabView.DataContext = tabItemView.DataContext = new ContextTabTabItemViewModel();
_regionManager.RegisterViewWithRegion("RibbonRegion", () => contextTabView);
_regionManager.RegisterViewWithRegion("TabRegion", () => tabItemView);
}
The first time the Command above is executed the DataContext of the views is set and then they are registered in the regions. This also sets the “IsSelected” property to true. If I change focus to the RibbonTab my ContextTab and TabItem loses focus and the “IsSelected” propery is set to false. If I press the button again the RequestNavigate is executed and once again the property is set to true. Here is my problem. If I do this a third time nothing happens! The RequestNavigate is executed but the property is not set to true and the Views does not regain focus. I am fairly new to PRISM and I am afraid that I am way off here. Any help would be appreciated.
In order to keep communication between ViewModels in a loosely coupled manner, you could simply use the EventAggregator and raise an event from the Command Button implementation, which would be then handled by the TabItemViewModel.
The solution you mentioned by adding one ViewModel into another would not be ideal as these components would end up working with tight coupling and defining an incorrect situation as Views/ViewModels would not depend on another View.
Therefore, to accomplish the EventAgregation approach, you would need to receive the EventAggregator from the container throw constructor on the View/ViewModel where the button is clicked, and on each one of the ViewModels you would want to subscribe to that event setting the IsSelected property inside the EventHandler method.
You could subscribe to the "GiveFocusEvent" event and handle it on the ViewModels which would set their IsSelected property as shown below:
public TabItemViewModel(IEventAggregator eventAggregator, ..){
...
GiveFocusEvent setFocusEvent = eventAggregator.Get<GiveFocusEvent>();
setFocusEvent.Subscribe(SetFocusEventHandler, ThreadOption.UIThread);
}
public void SetFocusEventHandler(){
// change IsSelected property value..
}
The Event would be published from inside the Button's CommandHandler method as follows:
this.eventAggregator.GetEvent<GiveFocusEvent>().Publish();
Notice that you would need to create and make your "GiveFocusEvent" event class inherit from CompositePresentationEvent:
public class GiveFocusEvent : CompositePresentationEvent<string>{}
I hope this helped you,
Regards.

Using viewmodel commands to open new tabs

I have an application where I have a main viewmodel whose view contains a tabcontrol, where each tab has its own view and viewmodel (and possibly more of them). I believe this is a pretty common design. Now, I want to open new tabs (by instantiating new viewmodels and adding them to the collection of workspaces) by firing commands from controls inside those tabs. The problem is that the command is received by the inner viewmodel, that controls the tab, not the outer one that controls the tabcontrol. What would be the best practice to do this? All the solutions I can think of are kind of "hacky" (giving the viewmodel a reference to its parent viewmodel, subscribing to a child's event from the parent...). I am assuming there is a nice solution for this.
For example, from a "list of entities" view, clicking the "new" button or selecting a row should open another tab with an "entity details" type of view. However, the command will be received by the "list of entities" view's viewmodel, to whom the tab is bound, and not the "list of workspaces" viewmodel to whom the tabcontrol is bound.
One possibility is to have your outer viewmodel expose a command to create a new tab. We use a centralized CommandService, which is simply a dictionary of name-to-ICommand, which allows for decoupled global commands. Something like this:
public interface ICommandService
{
void RegisterCommand(string name, ICommand command);
ICommand this[string name] {get;}
}
public class OuterViewModel
{
public OuterViewModel (ICommandService commandService)
{
commandService.RegisterCommand("OpenNewTab", OpenNewTab);
}
private void OpenNewTab (object newTabViewModel)
{
// The new tab's viewmodel is sent as the ICommand's CommandParameter
}
}
public class InnerViewModel
{
public InnerViewModel (ICommandService commandService)
{
_commandService = commandService; // Save injected service locally.
}
public HandleClickOnInnerTabpage()
{
AnotherViewModel newVM = new AnotherViewModel(...);
_commandService["OpenNewTab"].Execute(newVM);
}
}
You can use either standard .NET events (subscribing to the child events in the parent), or for more decoupling, use an event aggregator pattern.
Frameworks such as Prism and Caliburn.Micro implement the event aggregator pattern, and the MVVM Light Toolkit provides a Messenger class for the same purpose.

WPF , MVVM , MasterDetailPage

I got a MasterPage UserControl which contain 3 user controls (and 3 viewmodels)
MasterView:
-> MenuView (-> MenuViewModel )
-> InfoView (-> InfoViewModel )
-> SliderView (-> SliderViewModel )
In slider view i got a listbox with SelectedItem Property binded to a SelectedItem Property in the view model (SliderViewModel)
when the SelectedItem changes , i want to bubble it all the way to InfoViewModel and to update InfoView.
I can do it with events like i did many times in winforms but i'm looking for best practice way of the mvvm pattern.
another small question is , should i create a viewmodel for the MasterView page as well ? although it does not contain anything beside combining 3 users controls together.
your help will be greatly appreciated
I am not sure about the best MVVM best practise in this scenario but I can think of one way of doing it through MVVM.
You can create a MasterViewModel and expose a property in masterViewModel for communication between other view models. Like below
class MasterViewModel
{
MenuViewModel;
InfoViewModel;
SliderViewModel;
public CommunicationProperty
{
set
{
InforViewModel.SomeProperty = value;
}
}
}
class SliderViewModel
{
pubic SliderViewModel(MasterViewModel masterViewModel)
{
//hold reference of master view model in a variable
}
public SelectedItem
{
set
{
// change the info view model via master view model
masterViewModel.CommunicationProperty = value;
}
}
}
There is few options:
You may declare SelectedItem as dependency property in your view
model class (if it is inherited from DependencyObject)
You may declare SelectedItem as regular property and make your view model
class to implement INotifyPropertyChanged interface and fire
PropertyChanged event in setter.
WPF has new a lot of new concepts but still communicates with business logic via events.

MVVM and control creation

Imagine a simple scenario with a WPF window containing a button and some clear space. Clicking the button creates a new custom/user control and places it somewhere randomly on the window.
Clicking one of these controls will remove it from the window.
So now I have a ViewModel ala MVVM which exposes an ICommand for the "create new" button, but where does the code to create the new control live? Each control will probably have its own ViewModel which will handle its deletion and positioning I guess.
Can it be achieved with no code behind on the window AND no real knowledge of the View by the ViewModel?
The code that causes the controls to be created lives inside your "main" ViewModel.
The code that actually creates the controls is the container.
So it would go something like:
void AddControlCommandExecuted() {
var container = // resolve your DI container here
// Now use the container to resolve your "child" view. For example,
// if using UnityContainer it could go like this:
var view = container.Resolve<ChildView>();
// Of course you can also resolve the ViewModel if your program is
// "ViewModel-first" instead of "View-first".
// Does the ChildViewModel need some properties to be set?
var viewModel = (ChildViewModel)view.DataContext;
viewModel.StringProperty = "blah";
// Now get a reference to the region in your View which will host
// the "child" views.
var regionManager = container.Resolve<IRegionManager>();
var region = regionManager.Regions["MyRegionName"];
// Finally add the view to the region. You can do it manually, you
// can use the concept of "navigation" if your MVVM framework has one
// (I 'm using Prism, which does), etc etc.
region.Add(view);
}
Update: When writing the answer, I forgot that not all MVVM frameworks have Regions as Prism does. So excuse the specificity of the code above, as it doesn't really change anything. You simply need to build something like the Region abstraction yourself. Let's see:
class MyViewModel {
public event EventHandler<ChildViewModelAddedEventArgs> ChildViewModelAdded;
}
MyView would then attach an event handler to this event, and pick up the ChildView instance from inside ChildViewModelAddedEventArgs so that it can be added to an ItemsControl it is the parent of without your ViewModel messing with such details.
Of course this means that you now need some code-behind, but this cannot be helped unless you are using a framework that provides such services itself.
This SHOULD be doable with some very careful databinding on an ItemsControl, not sure how you would achieve the layout, but you will have a parent view model containing a collection of child view models, layout would then be preformed by the ItemsControl. When the parent ViewModel created the child ViewModel, it should inject a RelayCommand as a lambda expression to remove and cleanup the child ViewModel from the parents collection.

Categories

Resources