I am using a VS styled Tabcontrol (from the MahApps.Metro Project) in a Project with the Caliburn.Micro framework and I am looking for a way to let my ViewModel which inherits from Conductor.Collection.OneActive know when a Tab is being closed. Unfortunately the close button is already included in the style, and that is confusing me a bit. I looked up in the MahApps Source files for this VS Tabcontrol style, and found that each close button is bound to a CloseCommmand (Command="{Binding Path=CloseCommand}"). How can I react to a click of that button?
Attach DeactivateItem event to the close button.
<Button cal:Message.Attach="DeactivateItem($dataContext, 'true')" />
The DeactivateItem is a framework method of caliburn micro, defined in Conductor class.
This method will close the associated view and removes the view from Conductor Collection.
FYI:
Framework method.
public override void DeactivateItem(T item, bool close) {
if(item == null || !item.Equals(ActiveItem))
return;
CloseStrategy.Execute(new[] { ActiveItem }, (canClose, items) => {
if(canClose)
ChangeActiveItem(default(T), close);
});
}
Since the CloseTabCommand will trigger the Unloaded event, My workaround is attach a handler to it.
public partial class MyTab : MetroTabItem {
public MyTab() {
InitializeComponent();
this.Unloaded += dosomthing;
}
public void dosomething(Object sender, EventArgs e) {
//Your code
}
}
Related
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 created a new WPF MVVM application via Online Templates->WPF in VS2010->WPF MVVM project template. I created a checkbox labeled "Refresh Enabled?" next to the "Refresh" button that I wanted to enable/disable the "Refresh" button when clicked. I bound the IsChecked property of my checkbox to aMainWindowViewModel property I called CanRefreshDate, and it raises RaisePropertyChanged(()=>CanRefreshDate); in its setter. Also in the MainWindowViewModel, I added my newly created CanExecuteRefreshDate(), which returns the bool of CanRefreshDate property. However, when I click the checkbox, the button "Refresh" is never enabled/disabled to match. What is the proper way to fix this, and is this an oversight in the template or what?
Here's my modifications to the template code:
Xaml:
<CheckBox Content="Refresh Enabled?"
IsChecked="{Binding CanRefreshDate}"/>
MainWindowViewModel.cs:
private bool _CanRefreshDate;
public bool CanRefreshDate
{
get { return _CanRefreshDate; }
set
{
if (_CanRefreshDate != value)
{
_CanRefreshDate = value;
RaisePropertyChanged(() => CanRefreshDate);
}
}
}
public ICommand RefreshDateCommand { get { return new DelegateCommand(OnRefreshDate, CanExecuteRefreshDate); } }
private bool CanExecuteRefreshDate()
{
return CanRefreshDate;
}
I noticed that the template had RaiseCanExecuteChanged() misspelled RasieCanExecuteChanged() in DelegateCommand.cs and changed that. I was able to get it all working by removing RaiseCanExecuteChanged() and modifying the
public event Handler CanExecuteChanged;
to :
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
However, I would like to know what the proper solution for this is and why the template doesn`t work. Am i missing something, doing something wrong or what? Please create a new solution and use the template I did and tell me what is going on! Thanks!
The author fixed the issue and released version 4.1 of the template yesterday.
I would like on navigation between views in a WPF application using Prism to have the ability to set focus to sepecific textboxes so a user can perform navigation and then begin typing in the relevant textbox without a second click into the textbox.
I have an application built with Prism that has a Shell with a ContentControl "MainContentRegionContentControl". I then have some buttons across the top when on clicking them I do a region.RequestNavigate("UserControlToLoad"). On the UserControl I have the OnNavigatedTo and in that method I call this.MainTextBox.Focus().
The above doesn't appear to work, the navigation appears to work and the OnNavigatedTo method is called, but the textbox doesn't have focus.
I've added FocusManager.IsFocusScope="True" to the textbox, but this hasn't made a difference.
Use Loaded() method for each page instead of OnNavigatedTo() and TextBox.Focus() will work fine then ;)
Try the following... In your View implement IActiveAware
class ViewModel : IActiveAware
{
#region IActiveAware Members
private bool isActive = false;
public bool IsActive
{
get
{
return isActive;
}
set
{
if (value != isActive)
{
isActive = value;
OnIsActiveChanged(EventArgs.Empty);
}
}
}
public event EventHandler IsActiveChanged = delegate { };
protected virtual void OnIsActiveChanged(EventArgs args)
{
IsActiveChanged(this, args);
}
#endregion
}
OnIsActiveChanged, try setting focus to the TextBox you'd like to focus when IsActive becomes true
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();
}
}
I've got an issue with a custom control that I've written not firing it's ContextMenuOpening event when I hook it up programatically. The control is basically a wrapper for the standard TextBox:
public class MyTextBox : TextBox
{
public MyTextBox()
{
this.ContextMenuOpening += new ContextMenuEventHandler(MyTextBox_ContextMenuOpening);
}
void MyTextBox_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
MessageBox.Show("ContextMenuOpening event fired");
}
}
There's nothing suspect either about the XAML:
<local:MyTextBox Height="25" Width="300"/>
For some reason though, I can never get the event to fire. I'm trying to intercept the context menu so I can alter it (it's context sensitive) and really am trying to avoid having to hook up the event everywhere the control is used - surely this is possible?
Turns out you need to explicity set the ContextMenu to null when creating the object:
public MyTextBox()
{
this.ContextMenu = null;
this.Initialized += (s, e) =>
ContextMenuOpening += new ContextMenuEventHandler(MyTextBox_ContextMenuOpening);
}
Then it works a treat :)
The ContextMenuOpening-Event will only be fired after you assign a new context menu to the property ContextMenu:
public MyTextBox()
{
this.ContextMenu = new ContextMenu();
this.ContextMenu.Items.Add(new MenuItem {Header = "Do stuff"});
...
}