How can I cancel exiting from particular form after Cancel button (or X at the top right corner, or Esc) was clicked?
WPF:
<Window
...
x:Class="MyApp.MyView"
...
/>
<Button Content="Cancel" Command="{Binding CancelCommand}" IsCancel="True"/>
</Window>
ViewModel:
public class MyViewModel : Screen {
private CancelCommand cancelCommand;
public CancelCommand CancelCommand {
get { return cancelCommand; }
}
public MyViewModel() {
cancelCommand = new CancelCommand(this);
}
}
public class CancelCommand : ICommand {
public CancelCommand(MyViewModel viewModel) {
this.viewModel = viewModel;
}
public override void Execute(object parameter) {
if (true) { // here is a real condition
MessageBoxResult messageBoxResult = System.Windows.MessageBox.Show(
"Really close?", "Warning",
System.Windows.MessageBoxButton.YesNo);
if (messageBoxResult == MessageBoxResult.No) { return; }
}
viewModel.TryClose(false);
}
public override bool CanExecute(object parameter) {
return true;
}
}
Current code doesn't work. I want user to stay on current form if it chooses 'No' in popup dialog.
Also, overriding CanExecute doesn't help. It just disables the button. I want to allow user to hit the button, but then notify him/her, that data will be lost.
Maybe I should assign an event listener on button?
EDIT:
I managed showing popup on Cancel button. But I still can't manage Esc or X button (top right). It seems I was confused with Cancel button, because Execute method is executed when I click X button or Esc.
EDIT2:
I changed the question. It was 'how cancel Cancel button'. However, it wasn't what I was looking for. I need to cancel Esc or X button.
In 'MyViewModel' I add:
protected override void OnViewAttached(object view, object context) {
base.OnViewAttached(view, context);
(view as MyView).Closing += MyViewModel_Closing;
}
void MyViewModel_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
if (true) {
MessageBoxResult messageBoxResult = System.Windows.MessageBox.Show(
"Really close?", "Warning",
System.Windows.MessageBoxButton.YesNo);
if (messageBoxResult == MessageBoxResult.No) {
e.Cancel = true;
}
}
}
This solved my problem. However, I need ICommand to understand, which button was clicked, Save or Cancel. Is there any way to eliminate usage of event?
You are trying to do View's work in ViewModel class. Let your View class to handle the closing request and whether it should be canceled or not.
To cancel closing of a window you can subscribe to the Closing event of view and set CancelEventArgs.Cancel to true after showing a MessageBox.
Here is an example:
<Window
...
x:Class="MyApp.MyView"
Closing="OnClosing"
...
/>
</Window>
Code behind:
private void OnClosing(object sender, CancelEventArgs e)
{
var result = MessageBox.Show("Really close?", "Warning", MessageBoxButton.YesNo);
if (result != MessageBoxResult.Yes)
{
e.Cancel = true;
}
// OR, if triggering dialog via view-model:
bool shouldClose = ((MyViewModel) DataContext).TryClose();
if(!shouldClose)
{
e.Cancel = true;
}
}
I'm not an MVVM expert, but in my opinion Yusufs' answer isn't quite MVVM. On the other hand Torpederos answer is a bit complicated for only close cancellation. Here is my approach.
In this example I subscribed to the closing event, but it is always cancelled
private void OnClosing(object sender, CancelEventArgs e)
{
e.Cancel = true;
return;
}
In the XAML I added this
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding Close}" />
</i:EventTrigger>
</i:Interaction.Triggers>
And finally in the view model
public ICommand Close { get; set; }
Close = new RelayCommand(CommandClose);
private void CommandClose(object sender)
{
if (Dirty)
{
// Save your data here
}
Environment.Exit(0);
}
In this approach the the closing event is triggered first. That cancels the closing. After that the interaction trigger is invoked and triggers the code in the view model via the RelayCommand.
In the view model I can use the Dirty flag that is not accessible in the view.
Very good example of doing this in the View Model way can be found in the article of Nish Nishant, where he's using attached properties to hook up window events with commands.
Sample code of attached behaviour (author of the code: Nish Nishant)
public class WindowClosingBehavior {
public static ICommand GetClosed(DependencyObject obj) {
return (ICommand)obj.GetValue(ClosedProperty);
}
public static void SetClosed(DependencyObject obj, ICommand value) {
obj.SetValue(ClosedProperty, value);
}
public static readonly DependencyProperty ClosedProperty
= DependencyProperty.RegisterAttached(
"Closed", typeof(ICommand), typeof(WindowClosingBehavior),
new UIPropertyMetadata(new PropertyChangedCallback(ClosedChanged)));
private static void ClosedChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) {
Window window = target as Window;
if (window != null) {
if (e.NewValue != null) {
window.Closed += Window_Closed;
}
else {
window.Closed -= Window_Closed;
}
}
}
public static ICommand GetClosing(DependencyObject obj) {
return (ICommand)obj.GetValue(ClosingProperty);
}
public static void SetClosing(DependencyObject obj, ICommand value) {
obj.SetValue(ClosingProperty, value);
}
public static readonly DependencyProperty ClosingProperty
= DependencyProperty.RegisterAttached(
"Closing", typeof(ICommand), typeof(WindowClosingBehavior),
new UIPropertyMetadata(new PropertyChangedCallback(ClosingChanged)));
private static void ClosingChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) {
Window window = target as Window;
if (window != null) {
if (e.NewValue != null) {
window.Closing += Window_Closing;
}
else {
window.Closing -= Window_Closing;
}
}
}
public static ICommand GetCancelClosing(DependencyObject obj) {
return (ICommand)obj.GetValue(CancelClosingProperty);
}
public static void SetCancelClosing(DependencyObject obj, ICommand value) {
obj.SetValue(CancelClosingProperty, value);
}
public static readonly DependencyProperty CancelClosingProperty
= DependencyProperty.RegisterAttached(
"CancelClosing", typeof(ICommand), typeof(WindowClosingBehavior));
static void Window_Closed(object sender, EventArgs e) {
ICommand closed = GetClosed(sender as Window);
if (closed != null) {
closed.Execute(null);
}
}
static void Window_Closing(object sender, CancelEventArgs e) {
ICommand closing = GetClosing(sender as Window);
if (closing != null) {
if (closing.CanExecute(null)) {
closing.Execute(null);
}
else {
ICommand cancelClosing = GetCancelClosing(sender as Window);
if (cancelClosing != null) {
cancelClosing.Execute(null);
}
e.Cancel = true;
}
}
}
}
Example how to bind commands:
<Window
x:Class="WindowClosingDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:nsmvvm="clr-namespace:NS.MVVM"
nsmvvm:WindowClosingBehavior.Closed="{Binding ClosedCommand}"
nsmvvm:WindowClosingBehavior.Closing="{Binding ClosingCommand}"
nsmvvm:WindowClosingBehavior.CancelClosing="{Binding CancelClosingCommand}">
Commands "ClosedCommand", "ClosingCommand" and "CancelClosingCommand" should be defined in the separate View-Model.
internal class MainViewModel : ViewModelBase {
private ObservableCollection<string> log = new ObservableCollection<string>();
public ObservableCollection<string> Log {
get { return log; }
}
private DelegateCommand exitCommand;
public ICommand ExitCommand {
get {
if (exitCommand == null) {
exitCommand = new DelegateCommand(Exit);
}
return exitCommand;
}
}
private void Exit() {
Application.Current.Shutdown();
}
private DelegateCommand closedCommand;
public ICommand ClosedCommand {
get {
if (closedCommand == null) {
closedCommand = new DelegateCommand(Closed);
}
return closedCommand;
}
}
private void Closed() {
log.Add("You won't see this of course! Closed command executed");
MessageBox.Show("Closed");
}
private DelegateCommand closingCommand;
public ICommand ClosingCommand {
get {
if (closingCommand == null) {
closingCommand = new DelegateCommand(ExecuteClosing, CanExecuteClosing);
}
return closingCommand;
}
}
private void ExecuteClosing() {
log.Add("Closing command executed");
MessageBox.Show("Closing");
}
private bool CanExecuteClosing() {
log.Add("Closing command execution check");
return MessageBox.Show("OK to close?", "Confirm", MessageBoxButton.YesNo) == MessageBoxResult.Yes;
}
private DelegateCommand cancelClosingCommand;
public ICommand CancelClosingCommand {
get {
if (cancelClosingCommand == null) {
cancelClosingCommand = new DelegateCommand(CancelClosing);
}
return cancelClosingCommand;
}
}
private void CancelClosing() {
log.Add("CancelClosing command executed");
MessageBox.Show("CancelClosing");
}
}
This is another example of canceling the close window directly from ViewModel.
View:
<Window x:Class="WpfApplicationMvvmLight.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
Title="MainWindow" Height="350" Width="525">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding Path=ClosingCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<TextBlock>content...</TextBlock>
</Grid>
ViewModel:
using GalaSoft.MvvmLight.CommandWpf;
using System.ComponentModel;
using System.Windows;
namespace WpfApplicationMvvmLight
{
class SampleViewModel
{
public SampleViewModel() {
_closingCommand = new RelayCommand<CancelEventArgs>(OnClosingCommand);
}
private RelayCommand<CancelEventArgs> _closingCommand;
public RelayCommand<CancelEventArgs> ClosingCommand {
get {
return _closingCommand;
}
}
private void OnClosingCommand(CancelEventArgs e) {
//display your custom message box here..
var result = MessageBox.Show("Do you want to close?", "", MessageBoxButton.YesNoCancel);
//set e.Cancel to true to prevent the window from closing
e.Cancel = result != MessageBoxResult.Yes;
}
}
}
Code behind:
using System.Windows;
namespace WpfApplicationMvvmLight
{
public partial class MainWindow : Window
{
public MainWindow() {
InitializeComponent();
this.DataContext = new SampleViewModel();
}
}
}
This is the reference. MVVM close window event
Related
I have MaterialWindows from MaterialDesignInXaml which can be minimized in the notification bar.
I hide the windows by setting the Visibility to Collapsed.
The double click on the icon launches a command to restore the windows:
public RelayCommand OpenWindowsCommand => new RelayCommand(ExecuteOpenWindowsCommand);
private void ExecuteOpenWindowsCommand(object o)
{
WindowState = WindowState.Normal;
//Activated = true;
//IsInForeground = true;
//IsInForeground = false;
//IsFocus = true;
}
My property WindowState changed the visibility in it and notify the change with OnPropertyChanded.
The windows appear in the task bar but doesn't come on the foreground of my screen.
As you can see in comment, I tried different ways to get it without success like :
Focusable property
TopMost property
Top property
Even a behaviour to activate the windows
Is there something special to put a windows in the foreground of the screen?
Edit to add a sample repo :
MainWindow.xaml
<controls:MaterialWindow
x:Class="MinimiedWindows.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Behaviors="clr-namespace:MinimiedWindows.Behavior"
xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:MinimiedWindows"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tb="http://www.hardcodet.net/taskbar"
Title="MainWindow"
Width="800"
Height="450"
Focusable="{Binding IsFocus}"
Icon="app.ico"
Topmost="{Binding IsFocus}"
Visibility="{Binding WindowsVisibility, Mode=TwoWay}"
WindowState="{Binding WindowState}"
mc:Ignorable="d">
<i:Interaction.Behaviors>
<Behaviors:ActivateBehavior Activated="{Binding Activated, Mode=TwoWay}" />
</i:Interaction.Behaviors>
<Grid>
<tb:TaskbarIcon
x:Name="myNotifyIcon"
DoubleClickCommand="{Binding OpenWindowsCommand}"
IconSource="{Binding NotifyIcon}"
MenuActivation="LeftOrRightClick"
PopupActivation="DoubleClick"
ToolTip="aa"
Visibility="{Binding NotifyIconVisibility}" />
</Grid>
</controls:MaterialWindow>
MainWindows constructor set the datacontext to a new MainViewModel.
MainViewModel.cs :
(ViewModelBase is the classic one with the property OnPropertyChanged)
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
}
#region Windows properties
private WindowState _WindowState = WindowState.Normal;
public WindowState WindowState
{
get { return _WindowState; }
set
{
_WindowState = value;
OnPropertyChanged();
OnPropertyChanged("WindowsVisibility");
OnPropertyChanged("NotifyIconVisibility");
}
}
private Visibility _WindowsVisibility = Visibility.Visible;
public Visibility WindowsVisibility
{
get
{
if (WindowState == WindowState.Minimized)
{
return Visibility.Collapsed;
}
return Visibility.Visible;
}
set { _WindowsVisibility = value; }
}
private bool _Activated;
public bool Activated
{
get { return _Activated; }
set
{
_Activated = value;
OnPropertyChanged();
}
}
private bool _IsInForeground;
public bool IsInForeground
{
get { return _IsInForeground; }
set
{
_IsInForeground = value;
OnPropertyChanged();
}
}
private bool _IsFocus;
public bool IsFocus
{
get { return _IsFocus; }
set
{
_IsFocus = value;
OnPropertyChanged();
}
}
#endregion
#region NotifyBar
public string NotifyIcon
{
get { return "app.ico"; }
}
public Visibility NotifyIconVisibility
{
get
{
if (WindowState == WindowState.Minimized)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
}
public RelayCommand OpenWindowsCommand => new RelayCommand(ExecuteOpenWindowsCommand);
private void ExecuteOpenWindowsCommand(object o)
{
WindowState = WindowState.Normal;
Activated = true;
}
#endregion
}
And finally my ActivateBehavior.cs
public class ActivateBehavior : Behavior<MaterialWindow>
{
Boolean isActivated;
public static readonly DependencyProperty ActivatedProperty =
DependencyProperty.Register(
"Activated",
typeof(Boolean),
typeof(ActivateBehavior),
new PropertyMetadata(OnActivatedChanged)
);
public Boolean Activated
{
get { return (Boolean)GetValue(ActivatedProperty); }
set { SetValue(ActivatedProperty, value); }
}
static void OnActivatedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var behavior = (ActivateBehavior)dependencyObject;
if (!behavior.Activated || behavior.isActivated)
return;
// The Activated property is set to true but the Activated event (tracked by the
// isActivated field) hasn't been fired. Go ahead and activate the window.
if (behavior.AssociatedObject.WindowState == WindowState.Minimized)
behavior.AssociatedObject.WindowState = WindowState.Normal;
behavior.AssociatedObject.Activate();
}
protected override void OnAttached()
{
AssociatedObject.Activated += OnActivated;
AssociatedObject.Deactivated += OnDeactivated;
}
protected override void OnDetaching()
{
AssociatedObject.Activated -= OnActivated;
AssociatedObject.Deactivated -= OnDeactivated;
}
void OnActivated(Object sender, EventArgs eventArgs)
{
this.isActivated = true;
Activated = true;
}
void OnDeactivated(Object sender, EventArgs eventArgs)
{
this.isActivated = false;
Activated = false;
}
}
For the packages, I have installed Hardcodet.NotifyIcon.Wpf, MaterialDesignExtensionss 3.1.0 (and the materialDesign dependencies) and System.Windows.Interactivity.WPF.
Change your ExecuteOpenWindowsCommand to only set the Activated property.
private void ExecuteOpenWindowsCommand()
{
Activated = true;
}
Adapt the OnActivatedChanged method in the ActivateBehavior like this.
private static void OnActivatedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var behavior = (ActivateBehavior)dependencyObject;
if (!behavior.Activated || behavior.isActivated)
return;
// The Activated property is set to true but the Activated event (tracked by the
// isActivated field) hasn't been fired. Go ahead and activate the window.
var window = behavior.AssociatedObject;
if (window.WindowState == WindowState.Minimized)
{
window.WindowState = WindowState.Normal;
SystemCommands.RestoreWindow(window);
}
window.Activate();
}
The essential part is restoring the window, which did not happen before, it was hidden. Then it needs to be activated to come to foreground. The window state asssignment is not needed, but makes the restore transition of the window more pleasant.
EDIT: I have updated this with the two methods recommended
I am writing a simple custom PI (OSISoft) data viewer. I have two classes, one for the UI and one for the PI server interactions/program logic. The property for the data to be displayed has an event that fires when the property is changed. How do I get that change to propagate over to the UI class so the associated text box will automatically refresh?
Original code:
namespace PIViewer {
public partial class MainWindow : Window
{
ProgLogic logic;
public MainWindow() {
InitializeComponent();
logic = new ProgLogic();
InitializeValues();
}
private void InitializeValues() {
logic.SomeValue = logic.GetValFromServer(valueTag);
}
private void TextBoxSomeValue(object sender, TextChangedEventArgs e) {
// ??? something happens here?
}
}
public class ProgLogic {
private int someValue;
public event System.EventHandler SomeValueChanged;
protected void OnSomeValueChanged()
{
SomeValueChanged?.Invoke(this, EventHandlerArgs e);
}
public int SomeValue {
get => someValue;
set {
someValue = value;
OnSomeValueChanged();
}
}
public int GetValFromServer(string valueTag) {
int piValue;
piValue = PISDKMethodToGetServerValue(valueTag);
return piValue;
}
}
}
Method 1: Events
namespace PIViewer {
public partial class MainWindow : Window
{
ProgLogic logic;
public MainWindow() {
InitializeComponent();
logic = new ProgLogic();
logic.SomeValueChanged += Logic_SomeValueChanged;
InitializeValues();
}
private void Logic_SomeValueChanged(int obj) {
TextBoxSomeValue.Text = obj.toString();
}
private void InitializeValues() {
logic.SomeValue = logic.GetValFromServer(valueTag);
}
private void TextBoxSomeValue_TextChanged(object sender, TextChangedEventArgs e) {
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
logic.SomeValueChanged -= Logic_SomeValueChanged;
}
}
public class ProgLogic {
private int someValue;
public event Action<int> SomeValueChanged;
public virtual void OnSomeValueChanged(int newValue) {
SomeValueChanged?.Invoke(newValue);
}
public int SomeValue {
get => someValue;
set {
someValue = value;
OnSomeValueChanged(value);
}
}
public int GetValFromServer(string valueTag) {
int piValue;
piValue = PISDKMethodToGetServerValue(valueTag);
return piValue;
}
}
}
Method 2: MVVM pattern
MainWindow.xaml:
<Window
Closing="Window_Closing"
Title="My App">
<TextBox x:name="TextBoxSomeValue" text="{binding SomeValue, UpdateSourceTrigger=PropertyChanged}" />
</Window>
The important part here is the binding parameter in the text field of the TextBox definition, which points to the PropertyChangedEventHandler.
C# code:
namespace PIViewer {
public partial class MainWindow : Window
{
ProgLogic logic;
public MainWindow() {
InitializeComponent();
logic = new ProgLogic();
InitializeValues();
}
private void InitializeValues() {
logic.SomeValue = logic.GetValFromServer(valueTag);
}
private void TextBoxSomeValue_TextChanged(object sender, TextChangedEventArgs e) {
// run some other code when the text box updates
}
}
public class ProgLogic : INotifyPropertyChanged {
private int someValue;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public int SomeValue {
get => someValue;
set {
someValue = value;
OnPropertyChange("SomeValue")
}
}
public int GetValFromServer(string valueTag) {
int piValue;
piValue = PISDKMethodToGetServerValue(valueTag);
return piValue;
}
}
}
ProgLogic now implements INotifyPropertyChanged, which notifies the View of property changes, so that Bindings are updated.
I see you are heading the right way with C# event system. One thing I would change is event type from System.EventHandler to System.Action<int>. Even though people tend to propagate extending System.EventArgs class and writing custom delegates for handling events, using System.Action<T> is much easier to grasp for beginner.
So let's go with System.Action<int> example now. First, let's change ProgLogic class to be more like this:
public class ProgLogic
{
public event Action<int> SomeValueChanged;
//
// your other code goes here
//
private void OnSomeValueChanged(int newValue)
{
SomeValueChanged?.Invoke(newValue);
}
}
Now, you need to subscribe to the earlier written event in MainWindow class. So we do that as early as possible - in the constructor of MainWindow:
public MainWindow()
{
InitializeComponent();
logic = new ProgLogic();
logic.SomeValueChanged += OnSomeValueChanged;
InitializeValues();
}
Then, you describe your logic in the OnSomeValueChanged callback method, like:
private void OnSomeValueChanged(int newValue)
{
TextBoxSomeValue.text = newValue.ToString();
}
Make sure you unsubscribe from the event once MainWindow is getting destroyed to prevent memory leakage. This is just bare-bones for whole logic. I've left some space for interpretation. ;)
I'm not sure if I'm understanding the main point of your question but if you want to create a new value and have that value saved as the default value then you should create a string in your application setting and call on it on text changed.
At the top of your visual2019, in the menu options. open the debug menu and at the bottom you will see ("Your project name" + properties)
2.You will be brought into a new window with menu options on the left, go to the settings.
3.Create a string and set the value to "Some random text"
Note: In the example I placed one text box in front of the other, though this in not a great method it will prevent the text from appearing as a double or drawing a blank
Settings String Example
xaml
<Window x:Class="SaveNewText.MainWindow"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBox x:Name="DefaultText" Height="250" Width="250"
Background="Transparent"
Foreground="Black" MouseDown="TextBlock_MouseDown" IsReadOnly="True"/>
<TextBox x:Name="NewText" Height="250" Width="250" Background="Transparent"
Foreground="Black" TextChanged="NewText_TextChanged"/>
</Grid>
</Window>
xaml.cs
namespace SaveNewText
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DefaultText.Text = Properties.Settings.Default.TextString;
}
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
NewText.Focus();
}
private void NewText_TextChanged(object sender, TextChangedEventArgs e)
{
Properties.Settings.Default.TextString = NewText.Text;
Properties.Settings.Default.Save();
DefaultText.Text = Properties.Settings.Default.TextString;
}
}
}
I found a custom routed command example form the Microsoft Examples, It works well.
<Window x:Class="CustomRoutedCommand.MainWindow"
...
xmlns:local="clr-namespace:CustomRoutedCommand">
<Window.CommandBindings>
<CommandBinding Command="{x:Static local:MainWindow.ColorCmd}"
Executed="ColorCmdExecuted"
CanExecute="ColorCmdCanExecute"/>
</Window.CommandBindings>
The void ColorCmdExecuted(object sender, ExecutedRoutedEventArgs e), void ColorCmdCanExecute(object sender, CanExecuteRoutedEventArgs e) are defined in the MainWindow.cs.
How to change the XAML if I move these two handlers to xxxx.cs ?
Edit, Add more info
Command handlers are defined in MainWindow.cs, I cut and paste the code to another file as following, Then the compilation goes error. Error CS1061 'MainWindow' does not contain a definition for 'ColorCmdExecuted'
// xxxx.cs
namespace CustomRoutedCommand
{
public class xxxx
{
// ExecutedRoutedEventHandler for the custom color command.
private void ColorCmdExecuted(object sender, ExecutedRoutedEventArgs e)
{
var target = e.Source as Panel;
if (target != null)
{
target.Background = target.Background == Brushes.AliceBlue ? Brushes.LemonChiffon : Brushes.AliceBlue;
}
}
// CanExecuteRoutedEventHandler for the custom color command.
private void ColorCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Source is Panel)
{
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
}
}
You can't move the acual event handlers that you hook up in the XAML markup to another class, but you could implement the logic in another class:
public partial class MainWindow : Window
{
public static RoutedCommand ColorCmd = new RoutedCommand();
public MainWindow()
{
InitializeComponent();
}
private void ColorCmdExecuted(object sender, ExecutedRoutedEventArgs e)
{
xxxx.ColorCmdExecuted(e.Source);
}
private void ColorCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = xxxx.ColorCmdCanExecute(e.Source);
}
}
public class xxxx
{
public static void ColorCmdExecuted(object parameter)
{
var target = parameter as Panel;
if (target != null)
{
target.Background = target.Background == Brushes.AliceBlue ? Brushes.LemonChiffon : Brushes.AliceBlue;
}
}
public static bool ColorCmdCanExecute(object parameter)
{
return parameter is Panel;
}
}
You may want to replace the RoutedCommand with a custom implementation of the ICommand interface that can perform some action when you execute the command directly:
public class RelayCommand : ICommand
{
private Action<object> _execute;
private Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
if (_canExecute == null)
return true;
return _canExecute(parameter);
}
public void Execute(object parameter)
{
if (_execute != null)
_execute(parameter);
}
}
public partial class MainWindow : Window
{
public static RelayCommand ColorCmd = new RelayCommand(xxxx.ColorCmdExecuted, null);
public MainWindow()
{
InitializeComponent();
}
}
public class xxxx
{
public static void ColorCmdExecuted(object parameter)
{
var target = parameter as Panel;
if (target != null)
{
target.Background = target.Background == Brushes.AliceBlue ? Brushes.LemonChiffon : Brushes.AliceBlue;
}
}
}
XAML:
<Button Command="{x:Static local:MainWindow.ColorCmd}" Content="CommandTarget = FristStackPanel" />
Please refer to this blog post for more information about the concept.
You could continue to use static commands, but it is much more common to see an implementation of ICommand used called RelayCommand. This allows us to utilize the MVVM a little more easily so your view model model can take care of the commands. Here's a basic example implementation of ICommand:
public class RelayCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _execute;
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
_canExecute = canExecute;
_execute = execute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
Then, you would add the RelayCommand as a property to your view model such as like this:
public class ViewModel : ViewModelBase
{
private RelayCommand colorCmd;
public ICommand ColorCmd
{
get
{
colorCmd ?? colorCmd = new RelayCommand(p => ColorCmdCanExecute(), p => ColorCmdExectued());
return colorCmd;
}
}
private bool ColorCmdCanExecute()
{
//CanExecute code here
...
}
private void ColorCmdExecuted()
{
//Command execute code here
...
}
}
For more information on MVVM and ICommand implementation, there are lots of resources. This one is pretty easy to understand for a WPF beginner and should give you a bit more insight on how to proceed.
I am trying to make a usercontrol with some commands. If I wire up the commands in xaml using the approach shown here http://msdn.microsoft.com/en-us/library/vstudio/ms771361(v=vs.90).aspx it works but if I use the DelegateCommand from the Prism library CanExecuteChanged doesn't fire on the usercontrol and I cannot figure out why. I apologize I realize this is a lot of code. Execute fires correctly but CanExecute never does.
Thanks in advance.
Custom Control Xaml
<UserControl x:Class="Controls.LinkButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock>
<Hyperlink x:Name="hyperLink" Click="Hyperlink_Click">
<Run x:Name="textRun"
Text="Click Me"/>
</Hyperlink>
</TextBlock>
</UserControl>
Custom Control Code Behind
public partial class LinkButton : UserControl, ICommandSource
{
public LinkButton()
: base()
{
InitializeComponent();
textRun.DataContext = this;
hyperLink.DataContext = this;
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(LinkButton), new PropertyMetadata(null, new PropertyChangedCallback(CommandChanged)));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(LinkButton), new PropertyMetadata(null));
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(LinkButton), new PropertyMetadata(null));
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
if (Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
{
command.Execute(CommandParameter, CommandTarget);
}
else
{
((ICommand)Command).Execute(CommandParameter);
}
}
}
public static EventHandler canExecuteChangedHandler;
private static void CommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LinkButton lb = (LinkButton)d;
lb.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
}
// Add a new command to the Command Property.
private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
{
// If oldCommand is not null, then we need to remove the handlers.
if (oldCommand != null)
{
RemoveCommand(oldCommand, newCommand);
}
AddCommand(oldCommand, newCommand);
}
// Remove an old command from the Command Property.
private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = CanExecuteChanged;
oldCommand.CanExecuteChanged -= handler;
}
// Add the command.
private void AddCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
canExecuteChangedHandler = handler;
if (newCommand != null)
{
newCommand.CanExecuteChanged += canExecuteChangedHandler;
}
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (this.Command != null)
{
RoutedCommand command = this.Command as RoutedCommand;
// If a RoutedCommand.
if (command != null)
{
if (command.CanExecute(CommandParameter, CommandTarget))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
// If a not RoutedCommand.
else
{
if (Command.CanExecute(CommandParameter))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
}
}
}
Window
<ctrl:LinkButton Command="{Binding LinkClicked}"/>
public partial class MainWindow : Window
{
public MainWindow()
{
LinkClicked = new DelegateCommand(DoSomething, CanDoSomething);
InitializeComponent();
DataContext = this;
LinkClicked.RaiseCanExecuteChanged();
}
public DelegateCommand LinkClicked { get; set; }
public void DoSomething()
{
MessageBox.Show("Did Something");
}
public bool CanDoSomething()
{
return false;
}
}
The problem here is you are calling LinkClicked.RaiseCanExecuteChanged(); immediately after setting DataContext and till then the LinkedCommand is not binded and hence CanExecuteChanged event of the DelegateCommand is null and hence RaiseCanExecuteChanged() does not do anything. so to avoid this call LinkClicked.RaiseCanExecuteChanged() in the loaded event of Window because till then binding will be updated. Though this is a dirty solution because you will have to do this everywhere where you will use this LinkButton and bind its Command.
The implementation of RaiseCanExecuteChanged is something like this
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null) //CanExecuteChanged is coming null in your case so the event is not fired.
CanExecuteChanged(this, new EventArgs());
}
Or the better solution is that you havent called CanExecute in AddCommand method, In actual Command implementation CanExecute is called
if (newCommand != null)
{
newCommand.CanExecuteChanged += CanExecuteChanged;
newCommand.CanExecute(CommandParameter); //you are missing this.
}
If you do this then there is no need to call RaiseCanExecuteChanged.
Currenlty, I'm using as Below.
In xaml,
<Button Content="X" Width="33" Height="16" Padding="1,-2,1,0"
Command="{Binding ElementName=UserControlName, Path=DataContext.DenyCommand}"
<Button.CommandParameter>
<wpfext:UICommandParameter UICommandCallerCallback="{Binding ElementName=UserControlName, Path=UIDenyCallBackCommand}"/>
</Button.CommandParameter>
</Button>
In xaml.cs,
public UICommandCallerCallback UIDenyCallBackCommand
{
get;
private set;
}
public UserControlName()
{
this.UIDenyCallBackCommand = this.UIAccessDenyCallBack;
this.InitializeComponent();
}
public void UIAccessDenyCallBack(object commandParameter, object callbackData)
{
ShowADenyMsgBox();
}
private void ShowDenyMsgBox()
{
RightsDenied win = new RightsDenied(); //xaml window
win.Owner = GetImmediateWindow();
win.WindowStartupLocation = WindowStartupLocation.CenterScreen;
win.ShowDialog();
}
In ViewModel.cs,
internal ViewModel()
{
this.DenyCommand= new DenyCommand(this.AccessDeny);
}
public void AccessDeny(ICommandState commandState)
{
commandState.InvokeCallerCallback("AccessDenied");
}
public CommandCallback DenyCommand
{
get;
private set;
}
UICommandCallerCallback is declared as below.
public delegate void UICommandCallerCallback(object commandParameter, object callbackData);
CommandCallback class is as below.
public class CommandCallback:ICommand
{
private readonly Action<ICommandState> executeMethod;
private readonly Func<ICommandState, bool> canExecuteMethod;
public CommandCallback(Action<ICommandState> executeMethod)
: this(executeMethod, null)
{
}
public CommandCallback(Action<ICommandState> executeMethod, Func<ICommandState, bool> canExecuteMethod)
{
if (executeMethod == null)
{
throw new ArgumentNullException("executeMethod");
}
this.executeMethod = executeMethod;
this.canExecuteMethod = canExecuteMethod;
}
public bool CanExecute(object parameter)
{
return this.canExecuteMethod != null ? this.canExecuteMethod((ICommandState)parameter) : true;
}
public void Execute(object parameter)
{
if (parameter == null)
{
throw new ArgumentNullException("parameter","CommandCallback parameter cannot be null");
}
if (!(parameter is ICommandState))
{
throw new ArgumentException("expects a parameter of type ICommandState","parameter");
}
ICommandState state = (ICommandState)parameter;
this.executeMethod.Invoke(state);
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
}
It's working fine if it just to pop up the dialog box, but I want to wait for the result of the dialog and want to continue AccessDeny() function. For eg.
public void AccessDeny(ICommandState commandState)
{
1. processs
2. open xaml window and wait for the dialogresult. (i.e Yes No or Cancel)
3. Based on the result, continue processing.
}
What could be the best way to do this work flow? Please advise. Thanks.
Read through User Interaction Patterns in this documentation.