Using viewmodel commands to open new tabs - c#

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.

Related

WPF Adding tab items to tab control from multiple view models

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.

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.

Conditionally auto-click button in MVVM using xaml

I am writing an application based on MVVM architecture. The application has a Wizard like workflow. In couple of pages (views) in my application, I need a button to be auto-clicked when a certain condition is satisfied. The views are tied together using the root Wizard view model which has a ClickNextBtn command that is tied to the Next button in the root Wizard view. So, I need something like in the root Wizard view:
<DataTrigger Binding="{Binding Path=CanAutoClickNext}" Value="True">
<Setter Property="ClickBtn" Value="true" />
</DataTrigger>
The property in the above sample is meaningless, but hopefully it helps convey what I am trying to do.
The CanAutoClickNext bool property is available off of the Wizard view model.
On one of the views where I need the Next button auto-clicked, I tried passing the WizardViewModel as an argument to its corresponding view model's constructor when it is first instantiated in the root wizard view model, and then calling the ClickNextBtn off of it in a method therein later when the view is actually loaded. But that did not work, not surprisingly.
I know how to programmatically click a wpf button, but getting it all tied together in the framework I have is proving to a big challenge. Any feedback is appreciated.
UPDATE:
I ended up rewriting the UI design pattern (still MVVM) so that now instead of having to having to move to a next page automatically, the state within a page changes and a different set of controls become active. Users are then prompted to click next.
Like the comment's on your question stated, this should be a concern of the ViewModel to invoke the Click Handler.
How you could go about implementing this is very similar to something like this Question's answer
Now in MVVM, you should have your Button's connected to an ICommand in the ViewModel(If your using MVVM Light toolkit, it will be RelayCommand/RelayCommand<T>).
Now assuming this ICommand variable in your VM is called NextButtonCommand,
what you can do is
public bool CanAutoClickNext {
get {
return _canAutoClickNext;
}
private set {
if (value == _canAutoClickNext)
return;
_canAutoClickNext = value;
RaisePropertyChanged(() => CanAutoClickNext);
if (_canAutoClickNext)
NextButtonCommand.Execute(null);
}
}
with this, when your property in the VM CanAutoClickNext gets set to "True", the Execute function of the ICommand is automatically invoked by the VM. This seperates all the logic handling to the VM and keeps the View dumb as what is recommended by MVVM when it comes to application / business logic.
Side Note
The property CanAutoClickNext seems a waste if it's not being bound to anything from the View. If this is the case, I'd recommend just getting rid of that property and invoke the ICommand.Execute(null) from the place where the logic holds fit than use a property with INPC just for this case.
I'll follow up from a different angle. Let's say you have any message bus ready (IEventAggregator, IMessenger, doesn't matter). I'll use the Caliburn.Micro's IEventAggregator along with the nomenclature 'cause that's what I'm most familiar with. Now you might have a very simple event:
public class MoveNext
{
}
Then your 'host' viewmodel of the wizard:
public class WizardHost : IHandle<MoveNext>
{
private readonly IEventAggregator messageBus
public WizardHost(IEventAggregator messageBus)
{
this.messageBus = messageBus;
this.messageBus.Subscribe(this);
}
/here you might have the 'real' command method, e.g:
public void GoToNextQuestion()
{
// do stuff
}
public void Handle(MoveNext message)
{
GoToNextQuestion();
}
}
public class WizardPage
{
private readonly IEventAggregator messageBus;
private bool shouldMoveToNext;
public WizardPage(IEventAggregator messageBus)
{
this.messageBus = messageBus;
}
public void DoStuff()
{
//at some point, you might want to switch the flag or do whatever you need/want to do and:
if(shouldMoveToNext)
messageBus.Publish(new MoveNext());
}
}
Now when you DoStuff() in your wizard page, you can publish the event and the 'host' page will react and flip the page.
That's of course all nice if you're using any MVVM framework that's out there. MVVM Light has the Messenger, Caliburn.Micro has - as you might have noticed - IEventAggregator.

Updating a viewmodel from another viewmodel

I have two ViewModels one is attached to a main window and the other is attached to child window that is opened by clicking on a button on the main window. The child window contains a list of items and I want to select an item and display it in the main window by updating the main window viewmodel. What is the best way to accomplish this. Thanks!
There are any number of ways to do this.
Pass a reference to the main/parent view model into the child and have the child call the main view model.
Have the child view model fire an event that the parent subscribes to.
Use a messenger/mediator to communicate between the two. The parent subscribes, the child posts the message. This provides loose coupling.
Set the main view model up as a global service. Register it somehow. Have the child look up the service (requiring global services is a pretty common problem) and then call something on the global/common interface.
From my experience, the simplest method would be #2. Define an event on the child view model. The parent will have to look up the child (I don't know if it contains it or how your view models are constructed) and subscribe to the event.
The standard way to communicate between ViewModels is to use Messaging of some sort. One good implementation of this is the MVVM Light Toolkit
Here's some (random) code using the default messenger therefrom:
//Registering:
Messenger.Default.Register<IEnumerable<BookViewModel>>(this, true, fillSourceWith);
Messenger.Default.Register<DisplayModeEnum>(this, ChangeMainTemplates);
//with a specific "token"
Messenger.Default.Register<object>(this, MessageTokens.ClearList, o => BookSource.Clear());
//Sending
Messenger.Default.Send<List<BookViewModel>>(newBooks);
Messenger.Default.Send<DisplayModeEnum>(DisplayModeEnum.MediumLayout);
Messenger.Default.Send<object>(null, MessageTokens.ClearList);
Best way to do it is to have some kind of reference from child to parent and to update this parent when closing the child.
Or you can have some kind of event on child and let parent listen on this event. You then raise this event when child closes.
Messaging is used when both ViewModels are logicaly unrelated.
[rant] Dont people even know basic principles of OOP or what?? [/rant]
I had the same problem a few days before ;-)
Finally I used a mediator to communicate both view-models. On fact I used the Messenger from MVVM Light.
public void Search(object parameter)
{
ChildWindow window = new ChildWindow();
SearchWindow pageSearch = new SearchWindow();
window.Content = pageSearch;
window.Show();
Messenger.Default.Register<Messages.CloseWindowMessage>(this, action => this.closeWindow(action));
}
After that I defined the Message with everything I needed to know from main window:
public class CloseWindowMessage : MessageBase
{
public bool Result { get; set; }
public Model.Selected Selected { get; set; }
}
Finally the message back from the childwindow you only have to register the message with result and the object you want to get back.
You should register from the code-behind of your view to close the window.
I think best way to pass the message between two view models is event programming.

Having trouble deciding how to wire up a UserControl with MVVM

I've been doing the best I can to try to stay true to the separation recommended by the MVVM pattern. One thing I haven't figure out how to do correctly has to do with initializing my UserControls.
My most recent example of this has to do with a library that I wrote to talk to some low-level hardware. That assembly happens to have a UserControl that I can simply drop into any GUI that uses this hardware. All that is necessary for it to work is to set a reference to the object that has access to the low level methods.
However, that's where my problem lies -- currently, the UserControl is added to the GUI via XAML, where I define the namespace and then add the UserControl to my window. Of course, I have no control over its creation at this point, so the default constructor gets called. The only way to set the necessary reference for hardware control involves calling a method in the UC to do so. The ViewModel could feasibly call a method in the Model, e.g. GetController(), and then call the method in the UserControl to set the reference accordingly. The GUI can pass a reference to the UserControl to the ViewModel when said GUI creates the ViewModel, but this violates MVVM because the ViewModel shouldn't know anything about this control.
Another way I could deal with this is to not create the UserControl in XAML, but instead do it all from code-behind. After the ViewModel gets initialized and retrieves an initialized UserControl (i.e. one that has the low-level object reference set), it can set the Content of my Window to the UserControl. However, this also violates MVVM -- is there a way to databind the Content of a Window, TabControl, or any other element to a UserControl?
I'd like to hear if anyone has had to deal with this before, and if they approached it the first or second way I have outlined here, or if they took a completely different approach. If what I have asked here is unclear, please let me know and I'll do my best to update it with more information, diagrams, etc.
UPDATE
Thanks for the responses, guys, but I must not have explained the problem very well. I already use RelayCommands within the UserControl's ViewModel to handle all of the calls to the hardware layer (Model) when the user clicks in the control in the UserControl itself. My problem is related to initially passing a reference to the UserControl so it can talk to the hardware layer.
If I create the UserControl directly in XAML, then I can't pass it this reference via a constructor because I can only use the default constructor. The solution I have in place right now does not look MVVM-compliant -- I had to name the UserControl in XAML, and then in the code-behind (i.e. for the View), I have to call a method that I had added to be able to set this reference. For example, I have a GUI UserControl that contains the diagnostics UserControl for my hardware:
partial class GUI : UserControl
{
private MainViewModel ViewModel { get; set; }
public GUI( Model.MainModel model)
{
InitializeComponent();
ViewModel = new MainViewModel( model, this.Dispatcher);
ViewModel.Initialize();
this.DataContext = ViewModel;
diagnostics_toolbar.SetViewModel( ViewModel);
user_control_in_xaml.SetHardwareConnection( model.Connection);
}
}
where the outer class is the main GUI UserControl, and user_control_in_xaml is the UserControl I had to name in the GUI's XAML.
Looking at this again, I realize that it's probably okay to go with the naming approach because it's all used within the View itself. I'm not sure about passing the model information to user_control_in_xaml, because this means that a designer would have to know to call this method if he is to redo the GUI -- I thought the idea was to hide model details from the View layer, but I'm not sure how else to do this.
You will also notice that the main GUI is passed the Model in the constructor, which I assume is equally bad. Perhaps I need to revisit the design to see if it's possible to have the ViewModel create the Model, which is what I usually do, but in this case I can't remember why I had to create it outside of the GUI.
Am new to MVVM myself but here's a possible solution:
Create a property in your VM that is of the object type (that controls the hardware) and bind it to an attached property on your UserControl. Then you could set the property in your VM using dependency injection, so it would be set when the VM is created. The way I see it, the class that talks to the hardware (hardware controller) is a service. The service can be injected to your view model and bound to your UserControl. Am not sure if this is the best way to do it and if it is strict enough to all the MVVM principles but it seems like a possible solution.
if your question is: How do i show my viewmodel in the view? then my solution is always using viewmodelfirst approach and datatemplates.
so all you have to do is wire up your viewmodel via binding to a contentcontrol.content in xaml. wpf + datatemplates will do the work and instantiate your usercontrol for your viewmodel.
You are right, the ViewModel shouldn't know about anything in the View - or even that there is such a thing as a View, hence why MVVM rocks for unit testing too as the VM couldn't care less if it is exposing itself to a View or a test framework.
As far as I can see you might have to refactor things a little if you can. To stick to the MVVM pattern you could expose an ICommand, the ICommand calls an internal VM method that goes and gets the data (or whatever) from the Model, this method then updates an ObservableCollection property of the data objects for the View to bind to. So for example, in your VM you could have
private ICommand _getDataCommand;
public ICommand GetDataCommand
{
get
{
if (this._getDataCommand == null)
{
this._getDataCommand = new RelayCommand(param => this.GetMyData(), param => true);
}
return this._getDataCommand;
}
}
private void GetMyData{
//go and get data from Model and add to the MyControls collection
}
private ObservableCollection<MyUserControls> _uc;
public ObservableCollection<MyUserControls> MyControls
{
get
{
if (this._uc == null)
{
this._uc = new ObservableCollection<MyUserControls>();
}
return this._uc;
}
}
For the RelayCommand check out Josh Smiths MSDN article.
In the View you could either call the ICommand in the static constructor of your UC - I am guessing youwould need to add an event in your class for this - or call the ICommand from some sort of click event on your UC - maybe just have a 'load' button on the WPF window. And set the databinding of your UC to be the exposed observable collection of the VM.
If you can't change your UC at all then you could derive a new class from it and override certain behaviour.
Hope that helps a bit at least, like I say, have a look at Josh Smiths MVVM article as he covers the binding and ICommand stuff in there brilliantly.
If you set the DataContext of the Window or UserControl containing thisUserControl to the main view model, the user control can call SetHardwareConnection() on itself in its Loaded event (or DataContextChanged event handler).
If that's not possible because you're saying the UserControl is 'fixed', you should derive from it or wrap it up in another UserControl, which would serve as a MVVM 'adapter'.
(In order to bind the window: you could make the MainViewModel a singleton with a static Instance property and use DataContext="{x:Static MyClass.Instance}". A nice way to get things going quickly)
Note; this is based on my understanding that MVVM works because of Bindings.. I always bind the control to a ViewModel, not pass a ViewModel as a parameter.
Hope that helps!

Categories

Resources