My WPF maintenance window have a toolbar with a button "Exit"; a CommandExit is bind with this button. The CommandExit perform some checks before exit.
Now, if I click to close button of the window (x-button of title bar), this checks are ignored.
How can I do to bind CommandExit to window x-button?
I assume you may want to cancel closing based on these conditions? You need to use the Closing event, which passes you a System.ComponentModel.CancelEventArgs to cancel the close.
You can either hook this event in code-behind and execute the command manually, or, and this would be the preferred approach, you could use an attached behavior to hook the event and fire the command.
Something along the lines of (and I haven't tested this) :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Input;
namespace Behaviors
{
public class WindowCloseBehavior : Behavior<Window>
{
/// <summary>
/// Command to be executed
/// </summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(WindowCloseBehavior), new UIPropertyMetadata(null));
/// <summary>
/// Gets or sets the command
/// </summary>
public ICommand Command
{
get
{
return (ICommand)this.GetValue(CommandProperty);
}
set
{
this.SetValue(CommandProperty, value);
}
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Closing += OnWindowClosing;
}
void OnWindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (this.Command == null)
return;
// Depending on how you want to work it (and whether you want to show confirmation dialogs etc) you may want to just do:
// e.Cancel = !this.Command.CanExecute();
// This will cancel the window close if the command's CanExecute returns false.
//
// Alternatively you can check it can be excuted, and let the command execution itself
// change e.Cancel
if (!this.Command.CanExecute(e))
return;
this.Command.Execute(e);
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.Closing -= OnWindowClosing;
}
}
}
You have to implement event handler of your main window's event "Closing" where you can do checks and cancel the closing action. This is easiest way to do it, however otherwise you have to redesign whole window and its theme.
Related
I'm developing MVVM application and I'd like to create global shortcuts for my application. It's necessary to navigatate application between modules. I have main window with code:
<UserControl.InputBindings>
<KeyBinding Command="{Binding ChangeModuleComand}"
Key="M"
Modifiers="Control"/>
</UserControl.InputBindings>
It works when I have focus on my window,
But there is a problem when i have focus on TextBox in that UserControl (Command is not fired).
Is that possible to catch that key pressure without lots of modifications in whole application?
A.
It sounds like your event is being handled before it bubbles up that far. Next I am wondering if you could move your input binding up to your window rather than a user control. My final option would be to remove your input bindings and create a global check in code.
Since you're already working with input bindings, I have added the code option below:
//In App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
EventManager.RegisterClassHandler(typeof(UIElement), UIElement.KeyDownEvent, new RoutedEventHandler(GlobalClick));
}
private void GlobalClick(object sender, RoutedEventArgs e)
{
var args = (KeyEventArgs) e;
if (args.KeyboardDevice.IsKeyDown(Key.M) &&
args.KeyboardDevice.IsKeyDown(Key.LeftCtrl) || args.KeyboardDevice.IsKeyDown(Key.RightCtrl))
MessageBox.Show("Works");
}
Documenting this answer for others, as there is a much simpler way to do this that is rarely referenced, and doesn't require touching the XAML at all.
To link a keyboard shortcut at the Window level, in the Window constructor simply add a new KeyBinding to the InputBindings collection. As the command, pass in your arbitrary command class that implements ICommand. For the execute method, simply implement whatever logic you need. In my example below, my WindowCommand class takes a delegate that it will execute whenever invoked. When I construct the new WindowCommand to pass in with my binding, I simply indicate in my initializer, the method that I want the WindowCommand to execute.
You can use this pattern to come up with your own quick keyboard shortcuts.
public YourWindow() //inside any WPF Window constructor
{
...
//add this one statement to bind a new keyboard command shortcut
InputBindings.Add(new KeyBinding( //add a new key-binding, and pass in your command object instance which contains the Execute method which WPF will execute
new WindowCommand(this)
{
ExecuteDelegate = TogglePause //REPLACE TogglePause with your method delegate
}, new KeyGesture(Key.P, ModifierKeys.Control)));
...
}
Create a simple WindowCommand class which takes an execution delegate to fire off any method set on it.
public class WindowCommand : ICommand
{
private MainWindow _window;
//Set this delegate when you initialize a new object. This is the method the command will execute. You can also change this delegate type if you need to.
public Action ExecuteDelegate { get; set; }
//You don't have to add a parameter that takes a constructor. I've just added one in case I need access to the window directly.
public WindowCommand(MainWindow window)
{
_window = window;
}
//always called before executing the command, mine just always returns true
public bool CanExecute(object parameter)
{
return true; //mine always returns true, yours can use a new CanExecute delegate, or add custom logic to this method instead.
}
public event EventHandler CanExecuteChanged; //i'm not using this, but it's required by the interface
//the important method that executes the actual command logic
public void Execute(object parameter)
{
if (ExecuteDelegate != null) //let's make sure the delegate was set
{
ExecuteDelegate();
}
else
{
throw new InvalidOperationException("ExecuteDelegate has not been set. There is no method to execute for this command.");
}
}
}
I'm sure this can be used for other controls as well, but haven't tried it.
How to kill one button's event when a new button is clicked.
I have one event (Button G) running.(has a while loop waiting for some input).
I have a another button for quit operation.
Now. I cannot click any other button when button G's event is running.
How can I solve that?
Thanks
Hi, #Grokodile thank you for your code. So I commented your code here, Should I put my job logic code where I commented below? Thans
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private BackgroundWorker _worker;
public MainWindow()
{
InitializeComponent();
}
private void RunButtonClickHandler(object sender, RoutedEventArgs e)
{
_worker = new BackgroundWorker {WorkerSupportsCancellation = true};
_worker.DoWork += BackgroundWorkerTask;
_worker.RunWorkerAsync();
//I should Put my job logic here, right?
}
private void StopButtonClickHandler(object sender, RoutedEventArgs e)
{
if (_worker != null && _worker.IsBusy) _worker.CancelAsync();
//I should Put my job logic here, right?
}
private void BackgroundWorkerTask(object sender, DoWorkEventArgs e)
{
// this runs on the BackgroundWorker thread.
while (_worker.CancellationPending == false)
{
Thread.Sleep(500);
// You have to use the Dispatcher to transfer the effects of
// work done in the worker thread back onto the UI thread.
Dispatcher.BeginInvoke(new Action(UpdateTime), DispatcherPriority.Normal, null);
}
}
private void UpdateTime()
{
// Dispatcher runs this on the UI thread.
timeTextBlock.Text = DateTime.Now.ToString();
}
}
}
Futher to what H.B. and dowhilefor have said, here is a sample that shows starting a task on a background thread using BackgroundWorker with one Button and ending it with another Button, note the use of Dispatcher.BeginInvoke:
XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="80"
Width="640"
FontSize="16">
<DockPanel VerticalAlignment="Center">
<Button Margin="10,0"
x:Name="runButton"
DockPanel.Dock="Left"
Click="RunButtonClickHandler">Run</Button>
<Button Margin="10,0"
x:Name="stopButton"
DockPanel.Dock="Left"
Click="StopButtonClickHandler">Stop</Button>
<TextBlock Margin="10,0">The Time Is Now:</TextBlock>
<TextBlock x:Name="timeTextBlock"
Margin="10,0" />
</DockPanel>
</Window>
Code Behind
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private BackgroundWorker _worker;
public MainWindow()
{
InitializeComponent();
}
private void RunButtonClickHandler(object sender, RoutedEventArgs e)
{
_worker = new BackgroundWorker {WorkerSupportsCancellation = true};
_worker.DoWork += BackgroundWorkerTask;
_worker.RunWorkerAsync();
}
private void StopButtonClickHandler(object sender, RoutedEventArgs e)
{
if (_worker != null && _worker.IsBusy) _worker.CancelAsync();
}
private void BackgroundWorkerTask(object sender, DoWorkEventArgs e)
{
// this runs on the BackgroundWorker thread.
while (_worker.CancellationPending == false)
{
Thread.Sleep(500);
// You have to use the Dispatcher to transfer the effects of
// work done in the worker thread back onto the UI thread.
Dispatcher.BeginInvoke(new Action(UpdateTime), DispatcherPriority.Normal, null);
}
}
private void UpdateTime()
{
// Dispatcher runs this on the UI thread.
timeTextBlock.Text = DateTime.Now.ToString();
}
}
}
EDIT - A Little More Explanation
RunButtonClickHandler
Creates and initializes a BackgroundWorker so that it supports cancellation.
Attaches a DoWorkEventHandler to the DoWork event, i.e. BackgroundWorkerTask.
Starts excecution of the background operation with the call to RunWorkerAsync, i.e. creates a new thread (actually it uses a thread from the thread pool) and runs the code in BackgroundWorkerTask on that thread.
BackgroundWorkerTask
If you want to do work that would otherwise cause the UI to freeze when running on the main UI thread (e.g. search for undiscovered prime numbers) then you do it here in the background thread.
UpdateTime
All WPF Controls inherit from DispatcherObject and are associated with a Dispatcher which manages the execution of work done in the UI's single thread. If you need to do work such as setting the text of a TextBlock, you can't do it from the background thread, trying to do so will cause an exception to be thrown. UpdateTime is queued back onto the UI thread by the Dispatcher when Dispatcher.BeginInvoke is called from BackgroundWorkerTask. This way you can get the results of work done in the background before cancelling the background threads execution.
StopButtonClickHandler
Changes _worker.CancellationPending to true with the call to CancelAsync causing the while loop to exit and thus for execution to leave the BackgroundWorkerTask event handler.
In short, you can do work in two places, either in BackgroundWorkerTask or in UpdateTime, but you can only carry out changes to UI elements from UpdateTime.
Don't do spin-waiting operations on the UI-thread, use a new Thread or a Timer for example.
You can't. The UI is running in a single thread, usually called the main or ui thread. With your while loop you are blocking the whole ui thread, thus you can't receive any further input.
I suggest you check out BackgroundWorker class and maybe check some more MSDN articles about how the threading and background tasks should be designed to work properly in an ui enviroment.
In my WPF application there is a listbox with items. The listbox is populated via a xmldataprovider from XAML and then binding it to Itemssource property of the listbox.
Well, from XAML, I bind a comand to the listbox by doing:
<ListBox.CommandBindings>
<CommandBinding
Command="{x:Static local:mainApp.MyCmd}"
CanExecute="CanExecute"
Executed ="Executed" />
</ListBox.CommandBindings>
but I don't know how to programatically bind a command to each listboxitem. How to do it?
Thanks in advance.
First sorry by not posting it as a comment. I can't put all this in a comment.
Ok, yes I am not using the Executed and CanExecute properties of the ICommandSource despite I have registered and implemented them in custom class (in xaml they are commented too). I have specified them in routedCommand but not in custom class, I have done it in the constructor of the window by doing this:
WinMain code behind:
public WinMain()
{
InitializeComponent();
// Command binding. If I don't do this Executed and CanExecute are not executed
CommandBindings.Add(new CommandBinding(rcmd,
CommandBinding_Executed, CommandBinding_CanExecute));
}
and then I implement these methods in WinMain code behind too as it:
// ExecutedRoutedEventHandler
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
// Do stuff
}
// CanExecuteRoutedEventHandler
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// cBgWorkers is a class that check if a background worker is running
e.CanExecute = !cBgWorkers.isRunning;
//if (LayoutListBox != null) LayoutListBox.IsEnabled = !cBgWorkers.isRunning;
}
and In WinMain XAML I invoke the command like this:
<Classes:CommandListBox x:Name="LayoutListBox"
Command="{x:Static local:WinMain.rcmd}"
... >
<...>
</Classes:CommandListBox>
And in my custom class CommandListBox I have a CanExecuteChanged in which you can see I enabled or disabled the control depending on whether the background worker is finished or not:
private void CanExecuteChanged(object sender, EventArgs e)
{
this.Enabled = !cBgWorkers.isRunning;
}
but in custom class I haven't implement the event handler you say, OnSelected.
WIthout implementing it all goes ok, the custom control invoke command, and CanExecute method is reached, and CanExecute gets correct value, true or false depending on background worker is finished or not, and the CanExecuteChanged in custom control is raised when CanExecute changes its value. When background worker starts it gets disabled but when it finished it doesn't get enabled. I have debugged, and when background worker finishes I can see CanExecuteChanged is executed and this.Enabled is getting correct value (true) but for some reason in the UI the control continues disabled despite it gets its correct value and despite in RunWOrkerCompleted (in background worker) I force to update UI with CommandManager.InvalidateRequerySuggested().
I solve this by uncomment line:
if (LayoutListBox != null) LayoutListBox.IsEnabled = !cBgWorkers.isRunning;
in CanExecute method. I don't understand what happens.
Then If I do what you say It is not necessary to do it:
CommandBindings.Add(new CommandBinding(rcmd,
CommandBinding_Executed, CommandBinding_CanExecute));
and CommandBinding_Executed & CommandBinding_CanExecute implementations. Am I right?
but if I remove these methods where can I set this.enabled = !cBgWorkers.isRunning ?
I would like WPF to set automatically isEnabled property for my custom control. How to do this?
Thanks in advance.
I am applying the article you say about attached behaviors with a few changes to adapt it to my ListBox. It doesn't work well or perhaps I am doing something wrong. What I want is avoid that ListBox members (listBoxItems) can be select when a long task (background worker) is running. So one of the method of the article that I have modified is:
static void OnListBoxItemSelected(object sender, RoutedEventArgs e)
{
// Only react to the Selected event raised by the ListBoxItem
// whose IsSelected property was modified. Ignore all ancestors
// who are merely reporting that a descendant's Selected fired.
if (!Object.ReferenceEquals(sender, e.OriginalSource))
return;
ListBoxItem item = e.OriginalSource as ListBoxItem;
if (item != null)
{
// (*) See comment under
item.IsEnabled = !cBgWorkers.isRunning;
if (!cBgWorkers.isRunning)
{
item.BringIntoView();
}
}
}
(*) cBgWorkers is a public static Class that has some methods and properties.
One of the properties is isRunning that indicates no background workers is currently running. Then If no background workers is running, listbox members have to be enabled otherwise they have to be disabled so when user clicks over one listbox item the current page don't change to another one because I disabled it before (each listBox items has attached one page in my main application).
When one of the background workers (bw) or all are running and I select listbox item all is ok: listbox item is disabled because there are bw running and it avoid tho change the current page to another one. Of course, If I disabled the listbox item (or listbox items) I can't select it again because it is disabled and that is my problem, because I want that when bw finish the listbox items that have been disabled while bw were running, they get enabled again. Unfortunately with attached behavior as I see it isn't done by WPF automatically and commands have this advantage (controls update automatically by WPF). so, how to disabled/re-enabled listbox items when bw is running or not respectively?
As far as I know and see, one advantage of attached behaviors is that I think it is more efficient because they are not invoking actions constantly (only when the action, for example, selection, is produced). Commands are constantly (not often) checking if actions binded to controls can be executed (so If they can be executed, WPF automatically enables controls otherwise they appear disabled), right?
Thanks.
you could try creating a custom control that derives from ListBoxItem and implementing the ICommandSource interface. I can't think of a more simple solution as of now.
I have done your solution. I have done a custom user control deriving from listbox and implementing ISourceCommand as you said and it works now!!!! ;)
My custom class:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Input;
namespace GParts.Classes
{
public class CommandListBox : ListBox, ICommandSource
{
public CommandListBox() : base()
{
}
// ICommand Interface Members
// Make Command a dependency property so it can use databinding.
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(CommandListBox),
new PropertyMetadata((ICommand)null,
new PropertyChangedCallback(CommandChanged)));
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
// Make Command a dependency property so it can use databinding.
public static readonly DependencyProperty ExecutedProperty =
DependencyProperty.Register(
"Executed",
typeof(object),
typeof(CommandListBox),
new PropertyMetadata((object)null));
public object Executed
{
get
{
return (object)GetValue(ExecutedProperty);
}
set
{
SetValue(ExecutedProperty, value);
}
}
// Make Command a dependency property so it can use databinding.
public static readonly DependencyProperty CanExecuteProperty =
DependencyProperty.Register(
"CanExecute",
typeof(object),
typeof(CommandListBox),
new PropertyMetadata((object)null));
public object CanExecute
{
get
{
return (object)GetValue(CanExecuteProperty);
}
set
{
SetValue(CanExecuteProperty, value);
}
}
// Make CommandTarget a dependency property so it can use databinding.
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register(
"CommandTarget",
typeof(IInputElement),
typeof(CommandListBox),
new PropertyMetadata((IInputElement)null));
public IInputElement CommandTarget
{
get
{
return (IInputElement)GetValue(CommandTargetProperty);
}
set
{
SetValue(CommandTargetProperty, value);
}
}
// Make CommandParameter a dependency property so it can use databinding.
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(CommandListBox),
new PropertyMetadata((object)null));
public object CommandParameter
{
get
{
return (object)GetValue(CommandParameterProperty);
}
set
{
SetValue(CommandParameterProperty, value);
}
}
// Command dependency property change callback.
private static void CommandChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
CommandListBox clb = (CommandListBox)d;
clb.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;
//newCommand.Execute(null);
//newCommand.CanExecute(null);
}
// Add the command.
private void AddCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
canExecuteChangedHandler = handler;
if (newCommand != null)
{
newCommand.CanExecuteChanged += canExecuteChangedHandler;
//newCommand.Execute(Executed);
//newCommand.CanExecute(CanExecute);
}
}
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;
}
}
}
}
// Keep a copy of the handler so it doesn't get garbage collected.
private static EventHandler canExecuteChangedHandler;
}
}
and in my WinMain.xaml:
<Classes:CommandListBox x:Name="LayoutListBox"
Command="{x:Static local:WinMain.rcmd}"
<!-- These lines doesn't work I explain it following
Executed="CommandBinding_Executed"
CanExecute="CommandBinding_CanExecute"
-->
... >
<...>
</Classes:CommandListBox>
and window code behind:
public WinMain()
{
InitializeComponent();
// Command binding. If I don't do this Executed and CanExecute are not executed
CommandBindings.Add(new CommandBinding(rcmd,
CommandBinding_Executed, CommandBinding_CanExecute));
}
public static RoutedCommand rcmd = new RoutedCommand();
// ExecutedRoutedEventHandler
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
// Do stuff
}
// CanExecuteRoutedEventHandler
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !cBgWorkers.isRunning;
//if (LayoutListBox != null) LayoutListBox.IsEnabled = !cBgWorkers.isRunning;
}
but I have the same problem as the another solution. If I don't place the last line (here appears commented in CommandBinding_CanExecute) the listbox doesn't enable automatically by wpf when background worker finishes. If I put this line it works. What's happens?
Another thing, as you can see in my code snippet I would like to do the same as I do with a button where you can indicate command, executed and canexecute. I have registered them in the class, and in listbox I checked to pass the methods but it didn't work. How can I do this?
Thanks very much.
Take a look at attached behaviors.
According to the first question I posted, using CommandBindings in listbox it doesn't work. The implementation of CanExecute was:
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !cBgWorkers.isRunning;
}
By doing WPF doesn't enable/disabled the listbox control automatically depending on the background worker state (Running or not) and I don't understand why because I have other controls like buttons with commands binded and WPF automatically enable/disable them.
So I have done the following modification:
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !cBgWorkers.isRunning;
if (LayoutListBox != null) LayoutListBox.IsEnabled = !cBgWorkers.isRunning;
}
Now, it works. Listbox is enable when no background worker is running and disabled otherwise bu what I don't like is the last line placed into the method in where I enable/disable manually the property isEnabled of the listbox. It is inefficient so I would like to change isEnabled property of the listbox only when CanExecute changes its value. As far as I know there is a event to do this, it is CanExecuteChanged but I don't know how to implement it. Any ideas?
Now, after trying several solutions, I am implementing the Mike's solution because I think it is easier and clearer and it can be re-used for other controls with only a few changes.
I haven't been able the whole thread. It's quite long. Anyway, I thought you want to put a command on a ListBoxItem? From what I see, you inherited from ListBox. You do not need to specify the Executed and CanExecute properties of the ICommandSource. This should be specified in your RoutedCommand, not in your custom control. To get your command executed, you need to provide an event handler in your custom control. As an example, if an item gets selected, then you execute the command. Here's an example.
protected override void OnSelected(RoutedEventArgs e)
{
base.OnSelected(e);
if (this.Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
{
command.Execute(CommandParameter, CommandTarget);
}
else
{
((ICommand)Command).Execute(CommandParameter);
}
}
}
The basic idea behind a Cancel button is to enable closing your window with an Escape Keypress.
You can set the IsCancel property on
the Cancel button to true, causing the
Cancel button to automatically close
the dialog without handling the Click
event.
Source: Programming WPF (Griffith, Sells)
So this should work
<Window>
<Button Name="btnCancel" IsCancel="True">_Close</Button>
</Window>
However the behavior I expect isn't working out for me. The parent window is the main application window specified by the Application.StartupUri property. What works is
<Button Name="btnCancel" IsCancel=True" Click="CloseWindow">_Close</Button>
private void CloseWindow(object sender, RoutedEventArgs)
{
this.Close();
}
Is the behavior of IsCancel different based on whether the Window is a normal window or a Dialog? Does IsCancel work as advertised only if ShowDialog has been called?
Is an explicit Click handler required for the button (with IsCancel set to true) to close a window on an Escape press?
Yes, it only works on dialogs as a normal window has no concept of "cancelling", it's the same as DialogResult.Cancel returning from ShowDialog in WinForms.
If you wanted to close a Window with escape you could add a handler to PreviewKeyDown on the window, pickup on whether it is Key.Escape and close the form:
public MainWindow()
{
InitializeComponent();
this.PreviewKeyDown += new KeyEventHandler(CloseOnEscape);
}
private void CloseOnEscape(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
Close();
}
We can take Steve's answer one step further and create an attached property that provides the "escape on close" functionality for any window. Write the property once and use it in any window. Just add the following to the window XAML:
yournamespace:WindowService.EscapeClosesWindow="True"
Here's the code for the property:
using System.Windows;
using System.Windows.Input;
/// <summary>
/// Attached behavior that keeps the window on the screen
/// </summary>
public static class WindowService
{
/// <summary>
/// KeepOnScreen Attached Dependency Property
/// </summary>
public static readonly DependencyProperty EscapeClosesWindowProperty = DependencyProperty.RegisterAttached(
"EscapeClosesWindow",
typeof(bool),
typeof(WindowService),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnEscapeClosesWindowChanged)));
/// <summary>
/// Gets the EscapeClosesWindow property. This dependency property
/// indicates whether or not the escape key closes the window.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> to get the property from</param>
/// <returns>The value of the EscapeClosesWindow property</returns>
public static bool GetEscapeClosesWindow(DependencyObject d)
{
return (bool)d.GetValue(EscapeClosesWindowProperty);
}
/// <summary>
/// Sets the EscapeClosesWindow property. This dependency property
/// indicates whether or not the escape key closes the window.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> to set the property on</param>
/// <param name="value">value of the property</param>
public static void SetEscapeClosesWindow(DependencyObject d, bool value)
{
d.SetValue(EscapeClosesWindowProperty, value);
}
/// <summary>
/// Handles changes to the EscapeClosesWindow property.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> that fired the event</param>
/// <param name="e">A <see cref="DependencyPropertyChangedEventArgs"/> that contains the event data.</param>
private static void OnEscapeClosesWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Window target = (Window)d;
if (target != null)
{
target.PreviewKeyDown += new System.Windows.Input.KeyEventHandler(Window_PreviewKeyDown);
}
}
/// <summary>
/// Handle the PreviewKeyDown event on the window
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="KeyEventArgs"/> that contains the event data.</param>
private static void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
Window target = (Window)sender;
// If this is the escape key, close the window
if (e.Key == Key.Escape)
target.Close();
}
}
This isn't quite right is it...
MSDN says this: When you set the IsCancel property of a button to true, you create a Button that is registered with the AccessKeyManager. The button is then activated when a user presses the ESC key.
So you do need a handler in your code behind
And you don't need any attached properties or anything like that
Yes this is right.In windows Application in WPF AcceptButton and Cancel Button is there. But one thing is that if you are setting your control visibility as false, then it won't work as expected.For that you need to make as visibility as true in WPF. For example: (it is not working for Cancel button because here visibility is false)
<Button x:Name="btnClose" Content="Close" IsCancel="True" Click="btnClose_Click" Visibility="Hidden"></Button>
So, you need make it:
<Button x:Name="btnClose" Content="Close" IsCancel="True" Click="btnClose_Click"></Button>
Then you have write btnClose_Click in code behind file:
private void btnClose_Click (object sender, RoutedEventArgs e)
{ this.Close(); }
I'm trying to use the FolderBrowserDialog from my WPF application - nothing fancy. I don't much care that it has the Windows Forms look to it.
However, when I call ShowDialog, I want to pass the owner window which is an IWin32Window. How do I get this from my WPF control?
Actually, does it matter? If I run this code and use the ShowDialog overload with no parameters it works fine. Under what circumstances do I need to pass the owner window?
Thanks,
Craig
And here's my final version.
public static class MyWpfExtensions
{
public static System.Windows.Forms.IWin32Window GetIWin32Window(this System.Windows.Media.Visual visual)
{
var source = System.Windows.PresentationSource.FromVisual(visual) as System.Windows.Interop.HwndSource;
System.Windows.Forms.IWin32Window win = new OldWindow(source.Handle);
return win;
}
private class OldWindow : System.Windows.Forms.IWin32Window
{
private readonly System.IntPtr _handle;
public OldWindow(System.IntPtr handle)
{
_handle = handle;
}
#region IWin32Window Members
System.IntPtr System.Windows.Forms.IWin32Window.Handle
{
get { return _handle; }
}
#endregion
}
}
And to actually use it:
var dlg = new FolderBrowserDialog();
System.Windows.Forms.DialogResult result = dlg.ShowDialog(this.GetIWin32Window());
If you specify Owner, you will get a Modal dialog over the specified WPF window.
To get WinForms compatible Win32 window create a class implements IWin32Window like this
public class OldWindow : System.Windows.Forms.IWin32Window
{
IntPtr _handle;
public OldWindow(IntPtr handle)
{
_handle = handle;
}
#region IWin32Window Members
IntPtr System.Windows.Forms.IWin32Window.Handle
{
get { return _handle; }
}
#endregion
}
And use an instance of this class at your WinForms
IntPtr mainWindowPtr = new WindowInteropHelper(this).Handle; // 'this' means WPF Window
folderBrowserDialog.ShowDialog(new OldWindow(mainWindowPtr));
I realize this is an old question, but here is an approach which might be slightly more elegant (and may or may not have been available before)...
using System;
using System.Windows;
using System.Windows.Forms;
// ...
/// <summary>
/// Utilities for easier integration with WinForms.
/// </summary>
public static class WinFormsCompatibility {
/// <summary>
/// Gets a handle of the given <paramref name="window"/> and wraps it into <see cref="IWin32Window"/>,
/// so it can be consumed by WinForms code, such as <see cref="FolderBrowserDialog"/>.
/// </summary>
/// <param name="window">
/// The WPF window whose handle to get.
/// </param>
/// <returns>
/// The handle of <paramref name="window"/> is returned as <see cref="IWin32Window.Handle"/>.
/// </returns>
public static IWin32Window GetIWin32Window(this Window window) {
return new Win32Window(new System.Windows.Interop.WindowInteropHelper(window).Handle);
}
/// <summary>
/// Implementation detail of <see cref="GetIWin32Window"/>.
/// </summary>
class Win32Window : IWin32Window { // NOTE: This is System.Windows.Forms.IWin32Window, not System.Windows.Interop.IWin32Window!
public Win32Window(IntPtr handle) {
Handle = handle; // C# 6 "read-only" automatic property.
}
public IntPtr Handle { get; }
}
}
Then, from your WPF window, you can simply...
public partial class MainWindow : Window {
void Button_Click(object sender, RoutedEventArgs e) {
using (var dialog = new FolderBrowserDialog()) {
if (dialog.ShowDialog(this.GetIWin32Window()) == System.Windows.Forms.DialogResult.OK) {
// Use dialog.SelectedPath.
}
}
}
}
Actually, does it matter?
I'm not sure if it matters in this case, but generally, you should tell Windows what is your window hierarchy, so if a parent window is clicked while child window is modal, Windows can provide a visual (and possibly audible) clue to the user.
Also, it ensures the "right" window is on top when there are multiple modal windows (not that I'm advocating such UI design). I've seen UIs designed by a certain multi-billion dollar corporation (which shell remain unnamed), that hanged simply because one modal dialog got "stuck" underneath another, and user had no clue it was even there, let alone how to close it.
OK, figured it out now - thanks to Jobi whose answer was close, but not quite.
From a WPF application, here's my code that works:
First a helper class:
private class OldWindow : System.Windows.Forms.IWin32Window
{
IntPtr _handle;
public OldWindow(IntPtr handle)
{
_handle = handle;
}
#region IWin32Window Members
IntPtr System.Windows.Forms.IWin32Window.Handle
{
get { return _handle; }
}
#endregion
}
Then, to use this:
System.Windows.Forms.FolderBrowserDialog dlg = new FolderBrowserDialog();
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
System.Windows.Forms.IWin32Window win = new OldWindow(source.Handle);
System.Windows.Forms.DialogResult result = dlg.ShowDialog(win);
I'm sure I can wrap this up better, but basically it works. Yay! :-)
//add a reference to System.Windows.Forms.dll
public partial class MainWindow : Window, System.Windows.Forms.IWin32Window
{
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
var fbd = new FolderBrowserDialog();
fbd.ShowDialog(this);
}
IntPtr System.Windows.Forms.IWin32Window.Handle
{
get
{
return ((HwndSource)PresentationSource.FromVisual(this)).Handle;
}
}
}
VB.net translation
Module MyWpfExtensions
Public Function GetIWin32Window(this As Object, visual As System.Windows.Media.Visual) As System.Windows.Forms.IWin32Window
Dim source As System.Windows.Interop.HwndSource = System.Windows.PresentationSource.FromVisual(Visual)
Dim win As System.Windows.Forms.IWin32Window = New OldWindow(source.Handle)
Return win
End Function
Private Class OldWindow
Implements System.Windows.Forms.IWin32Window
Public Sub New(handle As System.IntPtr)
_handle = handle
End Sub
Dim _handle As System.IntPtr
Public ReadOnly Property Handle As IntPtr Implements Forms.IWin32Window.Handle
Get
End Get
End Property
End Class
End Module
Here is a simple method.
System.Windows.Forms.NativeWindow winForm;
public MainWindow()
{
winForm = new System.Windows.Forms.NativeWindow();
winForm.AssignHandle(new WindowInteropHelper(this).Handle);
...
}
public showDialog()
{
dlgFolderBrowser.ShowDialog(winForm);
}
The advantage of passing an owner handle is that the FolderBrowserDialog will not be modal to that window. This prevents the user from interacting with your main application window while the dialog is active.
You should be able to get an IWin32Window by by using PresentationSource.FromVisual and casting the result to HwndSource which implements IWin32Window.
Also in the comments here:
Why not using the built in WindowInteropHelper class (see namespace System.Windows.Interop). This class already impelements the IWin32Window ;)
So you can forget about the "OldWindow class" ... the usage stays the same