I'm new to MVVM and trying to figure out how to close a ChildWindow with the traditional Cancel button using MVVM Light Toolkit.
In my ChildWindow (StoreDetail.xaml), I have :
<Button x:Name="CancelButton" Content="Cancel" Command="{Binding CancelCommand}" />
In my ViewModel (ViewModelStoreDetail.cs), I have :
public ICommand CancelCommand { get; private set; }
public ViewModelStoreDetail()
{
CancelCommand = new RelayCommand(CancelEval);
}
private void CancelEval()
{
//Not sure if Messenger is the way to go here...
//Messenger.Default.Send<string>("ClosePostEventChildWindow", "ClosePostEventChildWindow");
}
private DelegateCommand _cancelCommand;
public ICommand CancelCommand
{
get
{
if (_cancelCommand == null)
_cancelCommand = new DelegateCommand(CloseWindow);
return _cancelCommand;
}
}
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
If you displayed your child window by calling ShowDialog(), then you can simply set the IsCancel property of your button control to "True".
<Button Content="Cancel" IsCancel="True" />
It becomes the same as clicking the X button on the window, or pressing ESC on the keyboard.
Have a look at this articleon MSDN. About half way down there is an approach on how to do this. Basically it uses either uses a WorkspaceViewModel or you implements an interface that exposes and event RequestClose
You then inside the Window's DataContext (if you are setting the ViewModel to it) you can attach to the event.
This is an excerpt from the article (Figure 7). You can adjust it to suit your needs.
// In App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow window = new MainWindow();
// Create the ViewModel to which
// the main window binds.
string path = "Data/customers.xml";
var viewModel = new MainWindowViewModel(path);
// When the ViewModel asks to be closed,
// close the window.
viewModel.RequestClose += delegate
{
window.Close();
};
// Allow all controls in the window to
// bind to the ViewModel by setting the
// DataContext, which propagates down
// the element tree.
window.DataContext = viewModel;
window.Show();
}
It's been a while since I've used WPF and MVVMLight but yes I think I'd use the messanger to send the cancel event.
In MVVM Light Toolkit the best what you can do is to use Messenger to interact with the View.
Simply register close method in the View (typically in the code behind file) and then send request to close a window when you need it.
We have implemented a NO-CODE BEHIND functionality. See if it helps.
EDIT: Here is there Stackoverflow discussion
Here are some ways to accomplish it.
Send message to your childwindow and set DialogueResult to false on childwindow code-behind.
Make property of DialogueResult and Bind it with childwindow Dialoue CLR property, set it on CancelEval method of CancelCommand.
Create object of Childwindow and set DialogueResult false on CancelEval.
Kind of late to the party but I thought I'd add my input. Borrowing from user841960's answer:
public RelayCommand CancelCommand
{
get;
private set;
}
Then:
SaveSettings = new RelayCommand(() => CloseWindow());
Then:
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
It's a bit cleaner than using an ICommand and works just as well.
So, to sum it all up, the example class would look like so:
public class ChildViewModel
{
public RelayCommand CancelCommand
{
get;
private set;
}
public ChildViewModel()
{
SaveSettings = new RelayCommand(() => CloseWindow());
}
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
}
Related
I have registered my views for the TabControl with Region manager and views are shown properly when tab is selected.
The problem is that when I select new tab item OnNavigatedTo is not called for that view or its view model.
I'm using PRISM 6.3
UPDATE
ViewModel
`public class ValuationViewModel : IViewModel, INavigationAware
{
private IRegionManager _regionManager;
public string Title { get; set; }
public ValuationViewModel(IRegionManager regionManager)
{
Title = "PERFORM VALUATION";
_regionManager = regionManager;
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
}`
View
`public partial class ValuationView : UserControl, IView
{
private IRegionManager _regionManager;
public ValuationView(ValuationViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
}
public IViewModel ViewModel
{
get
{
return (IViewModel)DataContext;
}
set
{
DataContext = value;
}
}
}`
Without code, nobody can give you the correct answer.
Its probably the best, if you show us your ViewModel for your "TabItem" View.
Assuming you registered your view and set your ViewModel in DataContext correctly, it could be possible that forget just a simple thing.
To manage your problem make sure you implemented the following things correctly:
Create a region for your TabControl
Register your view in that region
Make sure the DataContext is correctly set to your ViewModel
Make sure your ViewModel implemented INavigationAware
Update 1:
After testing a lot I found a simple answer unfortunately:
Members of INavigationAware (OnNavigatedTo, IsNavigationTarget & OnNavigatedFrom) are called when the NavigationService is navigating.
They aren't if you click on the TabItemHeader.
To solve your problem you have several options.
One option is to start a navigation request when the user click on the TabItemHeader ( bad approach).
In my opinion you should use the IActiveAware Interface ( https://msdn.microsoft.com/en-us/library/microsoft.practices.prism.iactiveaware(v=pandp.50).aspx).
It will solve your problem, because the navigation via RegionManager and the clicking on the TabItemHeader results in the same: INavigationAware.IsActive = true.
Now you are able to detect when your tab is shown or not and react.
In WPF application together with MVVMLight Toolkit, I would like to see your opinion, what is the best way to implement if I need to Cancel the Window Close event.
In Window.Closing event I can set the e.Cancel = true, which prevents closing the form. To identify if the Close is allowed, or should be prevented is in the ViewModel context.
One solution could be if I define an Application variable, and I can query this in the normal event handler in view code behind?
thanks
With MVVM Light you got EventToCommand:
So you could in xaml wire up the closing event to the VM.
<Window ...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding ClosingCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
and in the VM:
public RelayCommand<CancelEventArgs> ClosingCommand { get; private set; }
ctor() {
ClosingCommand = new RelayCommand<CancelEventArgs>(args => args.Cancel = true);
}
If you do not want to pass CancelEventArgs to the VM:
You could always take the similar approach with a Behavior and just use a simple bool from the VM(bind this bool to the Behavior) to indicate the closing event should be cancelled.
Update:
Download Link for following example
To do this with a Behavior you could just have a Behavior such as:
internal class CancelCloseWindowBehavior : Behavior<Window> {
public static readonly DependencyProperty CancelCloseProperty =
DependencyProperty.Register("CancelClose", typeof(bool),
typeof(CancelCloseWindowBehavior), new FrameworkPropertyMetadata(false));
public bool CancelClose {
get { return (bool) GetValue(CancelCloseProperty); }
set { SetValue(CancelCloseProperty, value); }
}
protected override void OnAttached() {
AssociatedObject.Closing += (sender, args) => args.Cancel = CancelClose;
}
}
Now in xaml:
<i:Interaction.Behaviors>
<local:CancelCloseWindowBehavior CancelClose="{Binding CancelClose}" />
</i:Interaction.Behaviors>
Where CancelClose is a bool property from the VM which indicates if the Closing event should be cancelled or not. In the attached example I have a Button to toggle this bool from the VM that should let you test the Behavior
You could to control this using Messages, for instance:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Messenger.Default.Register<CloseApplicationMessage>(this, m => Close());
Loaded += MainWindowLoaded;
Closing += MainWindowClosing;
}
private void MainWindowClosing(object sender, CancelEventArgs e)
{
//Ask for saving
var closingMessage = new ClosingApplicationMessage();
Messenger.Default.Send(closingMessage);
if (closingMessage.Cancel)
e.Cancel = true;
}
...
The mvvm message:
public class ClosingApplicationMessage
{
public bool Cancel { get; set; }
}
In this way, in any place you are listening to the ClosingApplicationMessage, you can control when the application is going to close, and may to cancel it.
Hope this helps...
I have a Floating-window template in which i load a Message-box by initializing the MessageBoxViewModel object to display the message
I want to close this pop up when user clicks on the Close button. How should i do this.
I have written the Close button command in the MessageBoxViewModel .
public class MessageBoxViewModel : ViewModelBase
{
public MessageBoxViewModel ( string messageText)
{
// load all the fields
}
}
private string message;
public string Message
{
get
{
return message;
}
set
{
if (value == message)
return;
message = value;
base.OnPropertyChanged("Message");
}
}
#region Commands
RelayCommand okay;
public ICommand OKAY
{
get
{
if (okay == null)
{
okay = new RelayCommand(
param => this.CallOkay()
);
}
return okay;
}
}
#endregion
void CallOkay()
{
// should write logic to close this window
}
The approach another MVVM framework uses (Caliburn Micro) is essentially just using events from the VM.
However, to extend the idea into a reusable 'module' Caliburn Micro uses a Conductor class which manages the relationship between the lifecycle of the View and the lifecycle of the ViewModel. An interface on the ViewModel which marks it as 'closable' is required, and you do need to write a conductor specific to the window/dialog implementation you are using (assuming it doesn't subclass from standard Window).
Somewhere in your code you have to create a window and bind it to the viewmodel. This is the place where the conductor should be created to manage the relationship (Caliburn has this in its IWindowManager implementation which provides and binds Window instances to a given VM when the ShowPopup/ShowDialog methods are called)
The conductor may look like (a contrived example):
public class WindowConductor
{
private ISupportClose _closeable;
private Window _window;
private bool _closingFromViewModel;
private bool _closingFromView;
public WindowConductor(Window view, ISupportClose closeable)
{
_closeable = closeable;
_window = view;
_window.Closed += WindowClosed;
_closeable.Closed += ViewModelClosed;
}
public void WindowClosed(object sender, EventArgs e)
{
if(_closingFromViewModel) return;
_closingFromView = true;
closeable.Close();
}
public void ViewModelClosed(object sender, EventArgs e)
{
if(_closingFromView) return;
_closingFromViewModel = true;
window.Close();
}
}
Your ISupportClose interface can simply be:
public interface ISupportClose
{
event EventHandler<CloseEventArgs> Closed;
void Close();
}
Then when you create your windows to display a view for a VM:
public void CreateWindow(viewModel)
{
Window window = new Window();
window.DataContext .. // etc etc bind up the view/model
// Wrap the window/vm with the conductor if the view supports the interface
var closeable = viewModel as ISupportClose;
if(closeable != null)
new WindowConductor(window, closeable);
}
I always find this very useful as it splits the concerns into smaller chunks. You don't often use more than 1 maybe 2 window implementations in an app anyway.
It may be worth noting that there is a bit of plumbing code behind all this (in fact a base class Screen provides a standard implementation of lifecycle management etc)
If you aren't using an MVVM framework, I'd highly recommend you do so - writing boilerplate 'glue' has been done already by multiple frameworks
The very nature of MVVM stipulates that the model knows nothing about the window that's reading it.
On solution is that the view model throws an event for the Window code to handle.
In your view model code:
public event EventHandler CallOkayRequested;
void CallOkay()
{
var dg = this.CallOkayRequested;
if(dg != null)
{
dg(this, EventArgs.Empty);
}
}
And in your window code, handle this event:
MyMessageBox()
{
InitializeComponent();
((MessageBoxViewModel)this.DataContext).CallOkayRequested += ModelCallOkayRequested;
}
void ModelCallOkayRequested(object sender, EventArgs args)
{
this.Close();
}
This might be the best way to do it, if, for example, the View Model is performing some other actions before wanting the dialog to close.
If, however, the view model is doing nothing other than relaying the request, it's less code if you bypass the model altogether and use a standard RoutedUICommand.
In your XAML declare a command binding:
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close" Executed="CloseCommandExecuted" />
</Window.CommandBindings>
Attach this command to your button:
<Button Command="ApplicationCommands.Close">
Close
</Button>
And handle the close method in your window code:
private void CloseCommandExecuted(object sender, EventArgs args)
{
this.Close();
}
There are many ways as referenced in Sriram Sakthivel's comment. But using view model event is simplest:
public event Action ViewModelClosed;
void CallOkay()
{
if (ViewModelClosed != null) ViewModelClosed();
}
in MessageBox's code behind:
...
MessageBoxViewModel vm = new MessageBoxViewModel();
vm.ViewModelClosed += () => this.Close();
Another way:
I always use a layer of message box in my view like this:
<UserControl>
<Grid>
<Border>
<!-- contents of my control -->
</Border>
<Border Visibility="{Binding IsVisible,
Converter={StaticResource BooleanToVisibilityConverter}}"
Background="#4000">
<!-- contents of my message box -->
</Border>
</Grid>
</UserControl>
Add a boolean (IsVisible) property to MessageBoxViewModel and bind the Visibility of MessageBox to it. Then simply change its value in CallOkay()
I have a listbox that is bound to a list of custom objects. I can get the listbox items to display correctly using the ListBox.ItemTemplate in xaml. The custom objects for the listbox are all of the same base class outlined below.
public class HomeViewMenuItem : UIElement
{
private Uri _uri;
private IRegionManager _manager;
public HomeViewMenuItem(string text, Uri uri, IRegionManager manager)
{
this.PreviewMouseDown += HomeViewMenuItem_PreviewMouseDown;
this.PreviewKeyDown += HomeViewMenuItem_PreviewKeyDown;
_manager = manager;
Text = text;
_uri = uri;
ClickCommand = new DelegateCommand(this.Click, this.CanClick);
}
void HomeViewMenuItem_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Enter)
{
e.Handled = true;
this.ClickCommand.Execute();
}
}
void HomeViewMenuItem_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true;
this.ClickCommand.Execute();
}
private void Click()
{
_manager.Regions[RegionNames.MainRegion].RequestNavigate(_uri);
}
private bool CanClick()
{
return true;
}
public DelegateCommand ClickCommand { get; set; }
public string Text { get; set; }
}
The problem I am having is the HomeViewMenuItem_PreviewKeyDown method is not getting called. I believe this is because the method is getting called on the ListBoxItem itself first and getting handled there. I was able to verify this by obtaining a reference to the ListBoxItem object through listBox.ItemContainerGenerator.ContainerFromIndex(0) after the ItemContainerGenerator status changes to ContainersGenerated and adding an event handler there. This event handler correctly fired. Normally this would be an ok solution on a small project but I plan on having more listboxes with the same sort of functionality and would like to have a simpler/better solution. Is there a way that I can get my base class previewkeydown method to work?
The only solution I could think of is to have the base class inherit from ListBoxItem instead of UIElement then get the ListBox to create my items instead of ListBoxItems. But I dont think that is really possible without creating my own ListBox implementation.
You seem to be somewhat confused. In WPF, we create data items and declare DataTemplates to define what those items should look like in the UI. Our data items do not extend UI classes. If you have to handle the PreviewKeyDown event, then attach a handler to the UI element in the DataTemplate instead:
<DataTemplate>
<Grid PreviewKeyDown="HomeViewMenuItem_PreviewKeyDown">
...
</Grid>
</DataTemplate>
How can I get Caliburn.Micro to map a key gesture to an action method on my ViewModel?
For example, I want to implement a tabbed interface, and I want my ShellViewModel to have a NewTab method, which the user should to be able to invoke by pressing Ctrl+T on the keyboard.
I know that the full Caliburn framework has support for gestures, but how can I do this using Caliburn.Micro? Is there perhaps some way to bind an action to a RoutedCommand (since RoutedCommands already support input gestures)? Or some other way to get gesture support?
I modified example to enable support for global key-bindings.
You just need to add the folowing code to your view:
<i:Interaction.Triggers>
<common:InputBindingTrigger>
<common:InputBindingTrigger.InputBinding>
<KeyBinding Modifiers="Control" Key="D"/>
</common:InputBindingTrigger.InputBinding>
<cl:ActionMessage MethodName="DoTheMagic"/>
</common:InputBindingTrigger>
</i:Interaction.Triggers>
And whenever Ctr+D is pressed the method DoTheMagic will be exexuted. Here is the modified InputBindingTrigger code:
public class InputBindingTrigger : TriggerBase<FrameworkElement>, ICommand
{
public static readonly DependencyProperty InputBindingProperty =
DependencyProperty.Register("InputBinding", typeof (InputBinding)
, typeof (InputBindingTrigger)
, new UIPropertyMetadata(null));
public InputBinding InputBinding
{
get { return (InputBinding) GetValue(InputBindingProperty); }
set { SetValue(InputBindingProperty, value); }
}
public event EventHandler CanExecuteChanged = delegate { };
public bool CanExecute(object parameter)
{
// action is anyway blocked by Caliburn at the invoke level
return true;
}
public void Execute(object parameter)
{
InvokeActions(parameter);
}
protected override void OnAttached()
{
if (InputBinding != null)
{
InputBinding.Command = this;
AssociatedObject.Loaded += delegate {
var window = GetWindow(AssociatedObject);
window.InputBindings.Add(InputBinding);
};
}
base.OnAttached();
}
private Window GetWindow(FrameworkElement frameworkElement)
{
if (frameworkElement is Window)
return frameworkElement as Window;
var parent = frameworkElement.Parent as FrameworkElement;
Debug.Assert(parent != null);
return GetWindow(parent);
}
}
Caliburn.Micro's Actions mechanism is built on top of System.Windows.Interactivity. So, you can create a custom trigger based on TriggerBase to do whatever you want, including global keyboard gestures. Then, just plug the ActionMessage into your trigger and viola!
Inherit from Caliburn's ActionMessage (which is a TriggerAction) and attach the derived trigger to the KeyDown event in XAML and set the ActionMessage.MethodName property. Add a property to the derived trigger of what key combination you are looking for and override the Invoke method to filter by that key combination, calling base.Invoke(...) if the key matches.
If you marshal a command through the View to the View Model you can control the CanExecute from the View Model. I've been using this method in multiple Caliburn projects. Might not be as "slick" as using Interactivity, but CanExecute works.
<UserControl x:Class="MyView"
...
Name="View"
>
<UserControl.InputBindings>
<KeyBinding Key="F5"
Command="{Binding RefreshCommand, ElementName=View, Mode=OneWay}" />
</UserControl.InputBindings>
<Button Command="{Binding Path=RefreshCommand, ElementName=View, Mode=OneWay}"/>
In your View class, you wire the command to the View Model which is referenced in the MyView.DataContext property.
Class MyView
Public Property RefreshCommand As _
New RelayCommand(AddressOf Refresh,
Function()
If ViewModel Is Nothing Then
Return False
Else
Return ViewModel.CanRefresh
End If
End Function)
Private Sub Refresh()
ViewModel.Refresh()
End Sub
Private ReadOnly Property ViewModel As MyViewModel
Get
Return DirectCast(DataContext, MyViewModel)
End Get
End Property
End Class