Our application is based on a stack of pages, which are just subclasses of a FrameworkElement. The main window maintains that stack and uses the built-in Close command to close them by simply popping them off the stack.
Now in some cases, the element being closed (or popped-off the stack) needs to do some cleanup first. Having that page also listen to the Close event seemed like the right thing to do.
Now, since that page would actually get the event before the window (the Close command is implemented via a 'bubbling event') we thought all we had to do was to set the command binding on the page, then in the handler, set e.Handled to false and it would continue up to the window.
Here's the code in the page (InitializeCommands is called from the constructor)...
private void InitializeCommands(){
CommandBindings.Add(
new CommandBinding(ApplicationCommands.Close, Close_Execute, Close_CanExecute)
);
}
private void Close_CanExecute(object sender, CanExecuteRoutedEventArgs e){
// True executes this handler, but blocks the one in the window
// False executes the one in the window, but ignores this one
e.CanExecute = true;
// Doesn't seem to have any effect
e.Handled = false;
}
private void Close_Execute(object sender, ExecutedRoutedEventArgs e){
Console.WriteLine("I want to respond passively!");
// Doesn't seem to have any effect
e.Handled = false;
}
However, regardless of what we set that property to, the command never makes it to the main window. If we remove the command binding in the page, it works again, proving the page is swallowing the command regardless of that property.
So what do you have to do to make the page listen to the Close event passively?
Yes, CommandBinding eats commands. Here's an excerpt of its implementation:
internal void OnExecuted(object sender, ExecutedRoutedEventArgs e)
{
if (!e.Handled)
{
if (e.RoutedEvent == CommandManager.ExecutedEvent)
{
if (this.Executed != null && CheckCanExecute(sender, e))
{
this.Executed(sender, e);
e.Handled = true;
}
}
else if (this.PreviewExecuted != null && CheckCanExecute(sender, e))
{
this.PreviewExecuted(sender, e);
e.Handled = true;
}
}
}
As you can see, if you return true for CanExecute, the commands get eaten.
You might want to take a look at CompositeCommand. That'd be more up your alley. You create a global CompositeCommand that is bound to the frame and then different views can attach to it. Different implementations can have different cool ways of determining how multiple subscribers to the commands behave. I.e. all must return canExecute, any must return, only goes to the active view, etc.
EDIT: CompositeCommand was originally part of Prism, but you can either find a standalone implementation or just yank the one from Prism itself:
https://github.com/PrismLibrary/Prism/blob/master/Source/Prism/Commands/CompositeCommand.cs
One additional idea is take a look at the AddHandler() method. That let's you add a single event handler for all the child events. I.e. for my bread crumb control, I can do:
AddHandler(BreadcrumbSplitButton.ClickEvent, new RoutedEventHandler(OnBreadcrumbSplitButtonClick));
In the BreadCrumb class to listen for the ClickEvent from all children BreadcrumbSplitButtons.
Related
I have 2 windows in a WPF application, and If I close the Main one, I want to close the other too.
I achieved this by setting in my App.xaml:
ShutdownMode="OnMainWindowClose"
The 2 windows have a Window_Closing event in which I de-initialise some components. Now what I want to achieve is: if I close the MainWindow, the Window_Closing event of the second window must execute before the closing event of the Main one, because the de-init of the second one has to be executed before the first. Any idea on how can I achive this?
Use the Application.Current.Windows collection. It contains the list of all the windows of your app; you can iterate through them and operate on them.
In this case, you can write:
public void MainWindow_Closing(object sender, CancelEventArgs e)
{
foreach (Window w in Application.Current.Windows)
{
if (w.Title == "Secondary Window") // <-- or whatever check you want to identify the secondary Window
{
w.Close();
break;
}
}
}
This allows you to gain a flexible behavior, for example you can close ALL the secondary windows, not just one.
Could you simply call the Close() function of the second window from the main window Window_Closing event?
void MainWindow_Closing(object sender, CancelEventArgs e)
{
secondaryWindow.Close();
...
}
Close all windows but the MainWindow in the Closing event handler. This should work:
void MainWindow_Closing(object sender, CancelEventArgs e)
{
foreach (Window w in Application.Current.Windows)
{
if (w != this)
w.Close();
}
//handle clean up for MainWindow here...
}
You might also want to consider to handle the Application.Current.Exit event to perform the cleanup of all windows in one place.
Hi I have a code to be written while window(WPF window) activate like clicking on the window or using alt/tab. The window is the child of a main form (windows app). I have used ToolWindow as the windowstyle.
It has a xamdatagrid which has to updated on activation
Problem is it fires multiple times. It should be fired once. I don not want my code to run multiple times
How to make it work. please help
From the Window.Activated Event page on MSDN:
Occurs when a window becomes the foreground window.
The Window.Activated Event is supposed to be called multiple times, so perhaps it is not the best event for you to handle. Alternatively, you could add a bool isFirstTime variable and use it to restrict your code to only being called once. Take this example:
private bool isFirstTime = true;
...
private void WindowActivated(object sender, EventArgs e)
{
if (isFirstTime)
{
isFirstTime = false;
// do something here just once
}
}
However, as (from the linked page)...
A window is activated (becomes the foreground window) when:
• The window is first opened.
• A user switches to a window by selecting it with the mouse, pressing ALT+TAB, or from Task Manager.
• A user clicks the window's taskbar button.
... you may find that this will not work for you.
I got it done.
I was using the below code
private void OnAttributeHistoryWindowActivated(object sender, EventArgs e)
{
var win = ((RoutedEventArgs)(e)).Source as AttributeHistoryWindow;
//My Code
}
The first line of code was firing back the Activated event. And it never goes to the next line of my code.
Now I used below code and it works.
private void OnAttributeHistoryWindowActivated(object sender, EventArgs e)
{
var win = sender as AttributeHistoryWindow;
//My Code
}
Now it fires once.
There is unfortunately no TabControl.SelectionChanging event (Selector.SelectionChanging), I am struggling to implement this behavior so I can cancel the changing request.
I tried to handle the TabControl.Items.CurrentChanging (the Items property is and ItemCollection) event setting e.Cancel (of the CurrentChangingEventArgs) to true, but the UI is is updated with the new tab although the item is not changed in the collection.
Is there any way to prevent user from switching to a different TabItem when a condition is dissatisfied?
I don't know the exact reason why this happens, and it annoys me greatly.
But here's my workaround for it:
In the sample below, checkbox is "locking" the current tab. So checked means user can't change tab.
void Items_CurrentChanging(object sender, CurrentChangingEventArgs e)
{
if (checkBox1.IsChecked.Value)
{
var item = ((ICollectionView)sender).CurrentItem;
e.Cancel = true;
tabControl1.SelectedItem = item;
}
}
Basically, what happens is (if I understand this correctly) the visual tree gets updated, but the logical tree does not. The above way forces the visual to sync with the logical tree.
You can also handle the PreviewLostKeyboardFocus event on each TabItem, and set the Handled property of the event arguments to true to prevent switching to another tab:
protected void tabItem_PreviewLostKeyboardFocus(object sender,
KeyboardFocusChangedEventArgs e)
{
if (!ValidateTabItem((TabItem) sender)) {
e.Handled = true;
}
}
See http://www.netframeworkdev.com/windows-presentation-foundation-wpf/how-to-cancel-navigation-between-tabitems-in-a-tabcontrol-84994.shtml.
I'm using the MVVM pattern in my first WPF app and have a problem with something quite basic I assume.
When the user hits the "save" button on my view, a command gets executed that calls the private void Save() in my ViewModel.
The problem is that the code in "Save()" takes some time to execute, so I'd like to hide the "Save" button in the UI view before executing the large chunk of code.
The problem is that the view doesn't update untill all code is executed in the viewmodel.
How can I force the view to redraw and process the PropertyChanged events before executing the Save() code?
Additionally, I would like a reuseable way, so that I can easily do the same thing in other pages as well.. Anyone else made something like this already? A "Loading..." message?
If it takes a long time, consider using a separate thread, for example by using a BackgroundWorker, so that the UI thread can stay responsive (i.e. update the UI) while the operation is performed.
In your Save method, you would
change the UI (i.e. modify some INotifyPropertyChanged or DependencyProperty IsBusySaving boolean which is bound to your UI, hides the Save button and maybe shows some progress bar with IsIndeterminate = True) and
start a BackgroundWorker.
In the DoWork event handler of your BackgroundWorker, you do the lengthy saving operation.
In the RunWorkerCompleted event handler, which is executed in the UI thread, you set IsBusySaving to false and maybe change other stuff in the UI to show that you are finished.
Code example (untested):
BackgroundWorker bwSave;
DependencyProperty IsBusySavingProperty = ...;
private MyViewModel() {
bwSave = new BackgroundWorker();
bwSave.DoWork += (sender, args) => {
// do your lengthy save stuff here -- this happens in a separate thread
}
bwSave.RunWorkerCompleted += (sender, args) => {
IsBusySaving = false;
if (args.Error != null) // if an exception occurred during DoWork,
MessageBox.Show(args.Error.ToString()); // do your error handling here
}
}
private void Save() {
if (IsBusySaving) {
throw new Exception("Save in progress -- this should be prevented by the UI");
}
IsBusySaving = true;
bwSave.RunWorkerAsync();
}
You're using MVVM pattern, so your Save Button's Command is set to an instance of the RoutedCommand object which is added to the Window's CommandBindings collection either declaratively or imperatively.
Assuming that you do it declaratively. Something like
<Window.CommandBindings>
<CommandBinding
Command="{x:Static namespace:ClassName.StaticRoutedCommandObj}"
CanExecute="Save_CanExecute"
Executed="Save"
/>
</Window.CommandBindings>
For the handler of Executed routed event, your Save() method, on entry, you set a variable to false, on return you set it back to true. Something like.
void Save(object sender, ExecutedRoutedEventArgs e)
{
_canExecute = false;
// do work
_canExecute = true;
}
For the handler of the CanExecute routed event, the Save_CanExecute() method, you use the variable as one of the condition.
void ShowSelectedXray_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = _canExecute && _others;
}
I hope I am clear. :)
You could always do something like this:
public class SaveDemo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _canSave;
public bool CanSave
{
get { return _canSave; }
set
{
if (_canSave != value)
{
_canSave = value;
OnChange("CanSave");
}
}
}
public void Save()
{
_canSave = false;
// Do the lengthy operation
_canSave = true;
}
private void OnChange(string p)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(p));
}
}
}
Then you could bind the IsEnabled property of the button to the CanSave property, and it will automatically be enabled/disabled. An alternative method, and one I would go with would be to use the Command CanExecute to sort this, but the idea is similar enough for you to work with.
You can accomplish this by the following code..
Thread workerThread = null;
void Save(object sender, ExecutedRoutedEventArgs e)
{
workerThread = new Thread(new ThreadStart(doWork));
SaveButton.isEnable = false;
workerThread.start();
}
do all your lengthy process in dowork() method
in some other method...
workerThread.join();
SaveButtton.isEnable = true;
This will cause to run save lengthy process in another thread and will not block your UI, if you want to show an animation while user click on save button then show some progress bar like iPhone etc... give me feedback i'll try to help you even more.
Late answer, but I figured it'd be good to input a bit as well.
Instead of creating your own new thread, it would probably be better to leave it up to the threadpool to run the save. It doesn't force it to run instantly like creating your own thread, but it does allow you to save threading resources.
The way to do that is:
ThreadPool.QueueUserWorkItem(Save);
The problem with using this approach, as well, is that you're required to have your "Save()" method take in an object that will act as a state. I was having a similar problem to yours and decided to go this route because the place that I'm working is very Resource-Needy.
XAML
<TreeView Name="GroupView" ItemsSource="{Binding Documents}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="OnTreeNodeDoubleClick"/>
</Style>
</TreeView.ItemContainerStyle>
....
</TreeView>
Code-Behind
private void OnTreeNodeDoubleClick(object sender, MouseButtonEventArgs mouseEvtArgs)
{
Console.WriteLine("{3} MouseDoubleClick Clicks={0} ChangedButton={1} Source={2} Handled={4} ButtonState={5}",
mouseEvtArgs.ClickCount, mouseEvtArgs.ChangedButton, mouseEvtArgs.OriginalSource,
mouseEvtArgs.Timestamp, mouseEvtArgs.Handled, mouseEvtArgs.ButtonState);
}
I find that for one double click, the event handler is called multiple times. I'm trying to open up a document in tab on a double-click on the corresponding tree node; so I'd need to filter out the extra calls.
23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed
23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed
In my slightly-complicated app, it is being raised 4 times per double-click. On a simple repro-app, it is being raised 2 times per double click. Also all the event argument parameters are the same too, so I can't distinguish the last one of a set.
Any ideas why this is the way it is?
I know this is an old question, but as I came across it in my searches for the solution, here are my findings for any future visitors!
TreeViewItems are recursively contained within each other. TreeViewItem is a HeaderedContentControl (see msdn), with the child nodes as the Content. So, each TreeViewItem's bounds include all of its child items. This can be verified using the excellent WPF Inspector by selecting a TreeViewItem in the visual tree, which will highlights the bounds of the TreeViewItem.
In the OP's example, the MouseDoubleClick event is registered on each TreeViewItem using the style. Therefore, the event will be raised for the TreeViewItems that you double-clicked on - and each of its parent items - separately. This can be verified in your debugger by putting a breakpoint in your double-click event handler and putting a watch on the event args' Source property - you will notice that it changes each time the event handler is called. Incidentally, as can be expected, the OriginalSource of the event stays the same.
To counter this unexpected behaviour, checking whether the source TreeViewItem is selected, as suggested by Pablo in his answer, has worked the best for me.
When a TreeViewItem is double clicked, that item is selected as part of the control behavior. Depending on the particular scenario it could be possible to say:
...
TreeViewItem tviSender = sender as TreeViewItem;
if (tviSender.IsSelected)
DoAction();
...
I've done some debugging and it appears to be a bug in WPF. Most answers already given are correct, and the workaround is to check if the tree view item is selected.
#ristogod's answer is the closest to the root problem - it mentions that setting e.Handled = true the first time handler is invoked doesn't have the desired effect and the event continues to bubble up, calling parent TreeViewItems' handlers (where e.Handled is false again).
The bug seems to be in this code in WPF:
http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,5ed30e0aec6a58b2
It receives the MouseLeftButtonDown event (which is handled by the child control already), but it fails to check if e.Handled is already set to true. Then it proceeds to create a new MouseDoubleClick event args (with e.Handled == false) and invokes that always.
The question also remains why after having set it to handled the first time the event continues to bubble? Because in this line, when we register the handler Control.HandleDoubleClick:
http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,40
we pass true as the last argument to RegisterClassHandler:
http://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/EventManager.cs,161
which is handledEventsToo.
So the unfortunate behavior is a confluence of two factors:
Control.HandleDoubleClick is called always (for handled events too), and
Control.HandleDoubleClick fails to check if the event had already been handled
I will notify the WPF team but I'm not sure this bug is worth fixing because it might break existing apps (who rely on the current behavior of event handlers being called even if Handled was set to true by a previous handler).
private void TreeView_OnItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.Source is TreeViewItem
&& (e.Source as TreeViewItem).IsSelected)
{
// your code
e.Handled = true;
}
}
This is not actually a bubbling issue. I've seen this before. Even when you tell the event that you handled it, it continues to keep bubbling up. Except that I don't think that it's actually bubbling up, but rather firing the node above's own double click event. I could be totally wrong on that. But in either case, it's important to know that saying:
e.handled = true;
Does nothing to stop this from happening.
One way to prevent this behavior is to note that when you are double clicking, you are first single clicking and that the selected event should fire first. So while you can't stop the Double Click events from occurring, you should be able to check inside the handler to see whether the event logic should run. This example leverages that:
TreeViewItem selectedNode;
private void MouseDoubleClickEventHandler(object sender, MouseButtonEventArgs e)
{
if(selectedNode = e.Source)
{
//do event logic
}
}
private void TreeViewSelectedEventHandler(object sender, RoutedEventArgs e)
{
selectedNode = (TreeViewItem)e.Source;
}
Sometimes however you have situations where the nodes are being selected by other beans than through the TreeView SelectedItemChanged event. In that case you can do something like this. If you happen to have a TreeView with a single declared top node, you can give that node a specific name and then do something like this:
bool TreeViewItemDoubleClickhandled;
private void MouseDoubleClickEventHandler(object sender, MouseButtonEventArgs e)
{
if (!TreeViewItemDoubleClickhandled)
{
//do logic here
TreeViewItemDoubleClickhandled = true;
}
if (e.Source == tviLoadTreeTop)
{
TreeViewItemDoubleClickhandled = false;
}
e.Handled = true;
}
Regardless of the method you use, the important thing is to note that for whatever reason with TreeViewItem double clicking that you can't stop the events from firing up the tree. At least I haven't found a way.
I have a little bit more elegant solution than checking for selection or creating flags:
A helper method:
public static object GetParent(this DependencyObject obj, Type expectedType) {
var parent = VisualTreeHelper.GetParent(obj);
while (parent != null && parent.GetType() != expectedType)
parent = VisualTreeHelper.GetParent(parent);
return parent;
}
And then your handler:
public void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is DependencyObject)
if (sender == (e.OriginalSource as DependencyObject).GetParent(typeof(TreeViewItem)))
{
// sender is the node, which was directly doubleclicked
}
}
This is the wonderful world of event bubbling. The event is bubbling up the node hierarchy of your TreeView and your handler is called once for every node in the hierarchy path.
Just use something like
// ...
if (sender != this)
{
return;
}
// Your handler code goes here ...
args.Handled = true;
// ...
in your handler code.
There are some pretty major problems with this solution, but it could work in case someone needs to solve this problem in multiple places and I did find a scenario where the accepted solution doesn't work (double clicking on a toggle button that opens up a popup, where the toggle button is inside another element that handles double click.)
public class DoubleClickEventHandlingTool
{
private const string DoubleClickEventHandled = "DoubleClickEventHandled";
public static void HandleDoubleClickEvent()
{
Application.Current.Properties[DoubleClickEventHandled] = DateTime.Now.AddSeconds(1);
}
public static bool IsDoubleClickEventHandled()
{
var doubleClickWasHandled = Application.Current.Properties[DoubleClickEventHandled] as DateTime?;
return doubleClickWasHandled.HasValue && !IsDateTimeExpired(doubleClickWasHandled.Value);
}
private static bool IsDateTimeExpired(DateTime value)
{
return value < DateTime.Now;
}
public static void EnableDoubleClickHandling()
{
Application.Current.Properties[DoubleClickEventHandled] = null;
}
public static bool IsDoubleClickEventHandledAndEnableHandling()
{
var handled = IsDoubleClickEventHandled();
EnableDoubleClickHandling();
return handled;
}
}
Use DoubleClickEventHandlingTool.HandleDoubleClickEvent()
inside the inner/low level element eg:
private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{if (e.ClickCount == 2) DoubleClickEventHandlingTool.HandleDoubleClickEvent();}
High level double click event then only performs it's action when:
if (!DoubleClickEventHandlingTool.IsDoubleClickEventHandledAndEnableHandling())
The most likely reason is that the doubleclick handler is installed multiple times, so each instance of the handler is being called once for each click.