I have a Caliburn.Micro shell (i.e., an empty XAML view to contain other views) rendered by a Conductor ViewModel. From there I open a Screen via:
ActivateItem(...)
Usually from the newly displayed dialog the user can perform some operations and click buttons (OK, Cancel, Build....) which should each transition to another screen (in the shell).
public MyDialog : Screen
{
public void Ok()
{
// TODO: Somehow tell the conductor or called of this class about this action.
}
}
What are good ways to achieve these kind of dialog action/message screen transitions?
Simple .NET events are possible -- Wouldn't that be a bad idea?
CM IEventAggregator should also work by changing the view
Checking from the shell Conductor the ViewModel result once it has been closed via TryClose() -- Should be possible, just don't know how to achieve this in CM.
Reference the shell Conductor instance from that screen (via IoC or directly) -- That seems strong coupling.
Could you please advise.
My preferred approach is to use the EventAggregator to facilitate messaging between VMs.
This works especially well when you have multiple windows which are listening for a certain type of event (e.g. a Visual Studio style interface with multiple tool windows which may show context sensitive properties), however it sounds a little overkill for this implementation. Of course the advantages are still a good loose coupling between VMs and a lack of events (which is a good thing!)
It sounds like you want a modal dialog to popup and present an option, and then activate another screen once the first one has returned.
You can attach an event handler to the Deactivated event in the child VM which will fire when an item deactivates. It also passes a boolean in the arguments to notify if the item which deactivated was closed - you can check for this and activate the corresponding screen in your conductor.
e.g.
this.Deactivated += new EventHandler<DeactivationEventArgs>(WorkspaceViewModel_Deactivated);
void WorkspaceViewModel_Deactivated(object sender, DeactivationEventArgs e)
{
if(e.WasClosed) // raise some event
}
Then pass an event up to the conductor, I wouldn't really go the event route for this. This couples the VMs one-way so it may not be the most flexible solution
The alternative is to fire a message via the event aggregator to tell the conductor it needs to open a different window when the child VM closes. The same method can be used but it's decoupled
this.Deactivated += new EventHandler<DeactivationEventArgs>(WorkspaceViewModel_Deactivated);
void WorkspaceViewModel_Deactivated(object sender, DeactivationEventArgs e)
{
if(e.WasClosed) MainConductor.EventAggregator.Publish(new ActivateWindowMessage(typeof(SomeVM));
}
Related
So I have a main window that shows MDI type interface with multiple document tabs open inside it (just like VS). Both the main window and the document windows have their respective VMs. The CloseDocument command is handled in the document, but needs to tell main window VM about it, so that main window VM could update its Documents collection. What is the proper way of managing this in MVVM? A few ideas that I have:
I could add an event to document VM that is raised just before closing. I could then add its event listener to main window VM for each new document that I add.
I could move the CloseDocument command to main window VM, but ideally the event doesn't belong there.
I could pass reference of the Documents collection to my document VM, so that it updates the collection before closing itself.
Which among these (or if someone has a better one) should be used while following MVVM practices?
I think I would pick solution 1. If you use MVVM Light then you can apply Messenger type to pass the information between Documents.
Each document would have a Command with reference to this method:
private void CloseDocumentExecuteCommand()
{
var message = new DocumentCloseMessage() { Document = this};
Messenger.Default.Send<DocumentCloseMessage>(message);
}
And in the VM of main Window you would have something like this:
(in constructor)
Messenger.Default.Register<CloseMessage>(this, (msgData) => this.CloseMessageReceived(msgData));
... but this could works only if you have Messenger, otherwise you could use events, but then I am afraid you need to use strong references between VMs.
I am trying to program in MVVM and I have the following use case:
A TextBox's text is bound to a property in the VM
A Button is command bound to a relay command
When the user presses the Button, the web browser's Navigate(url) method is called with the URL being the text in the TextBox
Above is the use case I want to create, but 1 and 2 is possible using the MVVM design pattern, but I could not find an adequate way to invoke the browser's Navigate() method. First of all, is it possible to call a method of a control from VM (please let me know if there is a way)? And in the above use case, what would be the appropriate way to structure the program if it is not possible?
Thanks
You could do the following:
Add a property MyUrl to your ViewModel
Bind MyUrl to your WebBrower's Source property
Make sure the property implements INotifyPropertyChanged. Then your Xaml:
<WebBrowser Source="{Binding MyUrl}" />
What if you REALLY wanted to call a UI method from the ViewModel?
If you ever do run into a situation where you absolutely need to call a method on a UI control for instance, you can hook up events on the ViewModel and then your UI registers to this event and does something UI specific...
VM code...
//... some VM logic
EpicNavigateEvent(url) // raise event, allowing UI to handle how
In your code-behind on your view (this is the part where some MVVM purests freak), you could register the event:
myVm.Navigate += doSomeNavigation;
...
public void doSomeNavigation(string url)
{
// call Navigate
}
I've successfully used this approach for applications where we have a single ViewModel layer and multiple technologies hooked up the views (WinForms, WPF and Asp.Net).
If you're looking for something more elegant, have a look at the User Interaction Patterns on MSDN.
The concept is the same though: Call something on the VM and the View is handles it appropriately.
Common scenarios for this type of approach is want to show a message to the user from the VM. Your VM should raise an event saying: ShowMyMessage("You're awesome"), then your UI is notified and handles it: MessageBox.Show(msg) or whatever.
As long as you stick to there rules you should be golden:
ViewModels should NOT be concerned about UI code
Views must ONLY handle the presentation of the data provided by your ViewModels.
Don't overcomplicate it. KISS...
I need to close a modal window from a ViewModel based on a click command that is triggered in a control that is presented within the window.
So, I have MainViewModel, JimViewModel, JimWindow and JimControl. MainViewModel creates a JimWindow and sets its DataContext to JimViewModel. JimWindow contains JimControl, which contains a button. When this button is clicked, I'd like to trigger a command that somehow closes JimWindow.
I've seen a few questions that answer this with respect to closing the Window from the actual Window (By passing the instance of the Window to a Command on the ViewModel), but it doesn't translate to what I want to do.
I'm not using a framework so I have no handy messenger to assist me. Can anyone help? Is it a case of somehow referencing the Name of the parent window from the control?
MainViewModel should not be creating windows, at least not directly. VMs should only create VMs. A window is part of the view world.
If you need your JimVM hosted in a window, then it would be better to have some kind of WindowService abstracted away behind an interface. MainVM then just creates JimVM and gives it to the window service to host in a window
Once you've got the windows bit decoupled into a separate service, then you can do all your crufty window stuff in there. I would have JimVM expose a CloseCommand and a Closed event. You can bind your JimControl button to the CloseCommand, and the windows service can subscribe to the Close event, and tear down the window when it fires.
This keeps the view and VM stuff completely separate. The only thing that knows how to glue the two together is the window manager.
Although idea of ViewModel creating a View sounds a little backwards, you could use messaging, e.g. TinyMessenger or Messenger that comes with MVVMLight.
You could then register for a message in your View/ViewModel and send it from-anywhere. A really simplistic example using MVVMLight could be:
// custom message
public class CloseMessage : MessageBase
{
public CloseMessage(object sender)
:base(sender)
{}
}
// main view registers for a message
public partial class MainWindow : Window
{
public MainWindow()
{
Messenger.Default.Register<CloseMessage>(this, message =>
{
// do teh stuff
});
}
...
}
// command bound to close button sends the message
private void YourCloseMainViewCommand()
{
Messenger.Default.Send(new CloseMessage(this));
}
I have just updated a project from Catel 3.4 to Catel 4.0 and a custom apply button that had been working now never gets enabled.
AddCustomButton(new DataWindowButton("Apply", ExecuteApply, canExecuteApply));
In Catel 3.4 the canExecuteApply got called when the window got focus or any control was changed. In 4.0 it gets called twice when the window is created and never again.
I suspect this has something to do with the IViewPropertySelector part of the update guide, however registering the default implementation had no effect and I can't figure out what namespace the AutoDetectViewPropertiesToSubscribe extension method is in.
Edit: I have found I am getting the same behavior with some AsynchronousCommand instances elsewhere in the application. The CanExecute delegate fires when the control is created then never again.
Edit 2: These were the same issue with diffrent solutions. For an explanation of the issue see Geert van Horrik's answer.
If the command is registered in a view model you can use
ViewModelCommandManager.InvalidateCommands(true);
to get the can execute state to re-evaluate. For a DataWindowButton as described above I had to manually call RaiseCanExecuteChanged on the button's command since that command does not belong to a vie model as far as i can tell.
var catelCommand = (applyButton.Command as ICatelCommand);
if (catelCommand != null)
{
catelCommand.RaiseCanExecuteChanged();
}
In either case, this is far from the approach with the best performance characteristics, but if the same behavior you had before the upgrade is desired, you can make these calls in the following event subscription:
System.Windows.Input.CommandManager.RequerySuggested += RequerySuggested;
Hope this helps anyone else facing this issue.
The reason is that in the past (pre 4.0), Catel subscribed to the CommandManager of WPF and invalidated all the commands on all view models on nearly everything (mouse move, focus, etc). To improve performance (a lot), we decided to only invalidate commands automatically when a property changes on a specific view model.
For example, if you have a vm where you change a property, it will automatically re-evaluate the commands on that vm. You can still manually re-evaluate commands using this code (inside a vm):
ViewModelCommandManager.InvalidateCommands(true);
How about this? I had a problem where my nested user controls must cause my outer user control's commands to update. It is not elegant but it gets the job done for me, until I can get a better way.
public partial class App : Application
{
private static IViewModelManager _ViewModelManager;
public App()
: base()
{
var dependencyResolver = this.GetDependencyResolver();
_ViewModelManager = dependencyResolver.Resolve<IViewModelManager>();
System.Windows.Input.CommandManager.RequerySuggested += RequerySuggested;
}
private void RequerySuggested(object sender, EventArgs e)
{
foreach (IViewModel viewModel in _ViewModelManager.ActiveViewModels)
{
(viewModel as ViewModelBase).GetViewModelCommandManager().InvalidateCommands(true);
}
}
}
In a MVVM WPF application.
How do you set a second windows parent from the ViewModel?
example:
view1 -- viewModel1
viewModel1's command calls:
var view2 = new view2
view2.Owner = <----This is the problem area. How do I get view1 as the owner here from the viewModel?
view2.Show()
EDIT:
See accepted answer below, then read the following edit.
I'am using MVVM light -> http://mvvmlight.codeplex.com/ (awesome btw)
The baked-in messaging system is great. I am now sending a message from the viewmodel to my view telling it to show another window.
For the message I'am currently using a string with a switch statement in the main view to determine what view to open; however I may tinker with the tokens that also are part of MVVM light toolkit.
Thank you!
In my opinion, opening a new window is the responsibility of the View, not of the ViewModel. Personally, I would use the same approach as used for displaying a dialog box (this was discussed in this forum already):
Have the ViewModel send a Message to the View requesting that it opens a new Window.
(alternatively) use an IDialogService or whatever you want to call it which you pass to the ViewModel's constructor. This service will be in charge of opening the Window (or of delegating this task to the View).
This way, you keep a clean separation of concerns and your VM remains testable (you can unit test that the request to open the new WIndow has been sent, but you couldn't test that the window has been, indeed, open).
Does that make sense?
Cheers,
Laurent
From your viewmodel call
Messenger.Default.Send<NotificationMessage>(new NotificationMessage("Open Window"));
And from your view's codebehind (a view that call the second
view) easily write this in the constructor:
Messenger.Default.Register<NotificationMessage>(this, ReplyToMessage);
And also write this method in the view's codebehind:
private void ReplyToMessage(NotificationMessage msg)
{
if (msg.Notification == "Open Window")
{
SecondWindow win = new SecondWindow();
win.ShowDialog();
}
}
I don't have an answer of my own but here's a few links to things I've been looking at lately that might help. I'll also be interested in anything others suggest.
As I understand it, the key thing is, you shouldn't be creating Views from within a View Model if possible, so you need a means of communicating what you need in a loosely coupled fashion.
http://www.codeproject.com/KB/WPF/XAMLDialog.aspx
http://www.codeproject.com/KB/architecture/MVVM_Dialogs.aspx
Handling Dialogs in WPF with MVVM
You can do in this way like you need to create some events and register those in view and call these in view model.and open that pop up window.
Like This example
public class Mainclass : MainView
{
public delegate abc RegisterPopUp(abc A);
public RegisterPopUp POpUpEvent;
public RelayCommand ShowCommand { private set; get; }
public void ShowCommand()
{
ShowCommand("Your parameter");
}
}
inside the view
MainView mn = new MainView();
Register the event here like mn.POpUpEvent += then click on tab button double time and in registers popup method write the code for opening the pop up window.
Prism-Event Aggrigator is good approach, where we can create independent module without dependency. first viewmodel will publish event and then another view or view or viewmodel can subscribe that event from event aggrigator.
in this case Unity container can also use to inject one viewmodel in to another with dependency injection.