When IAttachedObject.Detach() (KeyTrigger.Detaching()) method is actually called? - silverlight

Let's say I have a Silverlight user control with a textbox:
<TextBox>
<ii:KeyTrigger Key="Enter">
<!-- something here -->
</ii:KeyTrigger>
</TextBox>
I inject my user control into root content control:
root.Content = new MyUserControl();
Now I know that KeyTrigger will be hooked up to TextBox when "Loaded" event of MyUserControl occurs:
AssociatedObject.KeyDown+=SomethingHere;
Where AssociatedObject will be a TextBox in this case.
The event is unsubscribed in OnDetaching method overload of KeyTrigger, the problem is that i dont know when OnDetaching is being called... Anyone could shed some light on this?
I have a button that injects a new instance of user control into root content:
root.Content = new MyUserControl();
everytime this happens "Loaded" event occurs on new instance and event for KeyTrigger is called, no OnDetaching is called for "old" user control instance however... won't this cause memory leaks?
Is OnDetaching (Detach) called when a control dissapears from visual tree?

You want to handle the FrameworkElement.Closed event - OnDetached only gets called when the trigger is explicitly removed from its DependencyObject.

Related

WPF Control Loaded Event fires before children are created

I have an event handler for the "Loaded" event of a control in which I alter the properties of the one of the visual children. For years this has worked fine for me; My handler gets the child of the type it needs, alters the padding and all is well.
But now I've started using this control on a page of a TabControl. The page is one of several I create at once and it is not initially showing. For this page, the Loaded event fires before the user activates the page. But when my Loaded handler is called, there are literally zero visual children. The VisualTreeHelper reports that the count of children is 0.
Yet later on when click on the tab and make the page visible, I can see the control and all of its children have been created. So apparently they've been created after the Loaded event is fired.
Is there some other event I can hook on to to be sure that a control's children have been created?
This is the XAML I use
<tk:RadColorPicker
Margin="5,0"
SelectedColor="{Binding Settings.LiveImageHighlightColor, Mode=TwoWay}"
Loaded="RadColorPicker_OnLoaded"/>
And this is the handler:
private void RadColorPicker_OnLoaded(object sender, RoutedEventArgs e)
{
if (!(sender is RadColorPicker rcp))
return;
var count = VisualTreeHelper.GetChildrenCount(rcp);
// Other code here where I get the child of the type I want and change its padding
// But it's irrelevant to this question because the count = 0 in the error case
}
I've read the MS guidance on this and several threads on SO. They all point to "Loaded" as the event I want

Catching notifications from INotifyPropertyChanged before WPF windows close

In my current application, I have a WPF window with a large number of text fields for the user to fill in. To try and avoid loss of data, I've implemented a feature to notify users if they have unsaved changes.
The underlaying data object in the ViewModel implements INotifyPropertyChanged. So on each change I can set a boolean property on my BaseViewModel to indicate that something has changed. However, this notification only happens when the user moves off any given textbox and onto another part of the window.
The BaseViewModel also has an overridable SaveChanges method, in which the property is reset to indicate the data has been saved.
We're using MVVM, but in a nod to practicality, we do have one bit of code-behind. In the underlying view there's an assignment of a function to the Closing event. In the function, we check the boolean and give the user a messagebox to warn of unsaved changes.
This all works fine in the majority of situations. However, if someone alters text in a box and then goes straight for the close button on the WPF window, the property changed event gets fired after the window closes.
Is there any way I can catch this event and stop the window closing in this eventuality?
However, this notification only happens when the user moves off any given textbox and onto another part of the window.
You can control this using the UpdateSourceTrigger property.
<TextBox Text="{Binding Width, UpdateSourceTrigger=PropertyChanged}" Width="50" />
For a TextBox, the default value for this property is LostFocus, please refer to the Remarks section of the link.

WPF close window when property in ViewModel changes

I was wondering if there was a way to close a window when a property in the view model changes. In my situation I have a login window with an Ok button bound to a LoginCommand so that the function Login executes when Ok is clicked. If the login is successful, I want the window to close.
Now I know I could do this by adding an event handler on my button, which calls a function like this:
private void Button_Click(object sender, RoutedEventArgs e)
{
DatabaseCredentialsViewModel vm = (this.DataContext as DatabaseCredentialsViewModel);
vm.Login();
if (vm.LoginSuccessful)
{
this.Close();
}
}
But I was wondering if there was a way to close the window when LoginSuccessful property changes without having an event handler on my button (I like working only with command binding and not having event handlers on Click event).
Thank you
Here's a similar question, which filled my need.
Basically, you use an attached property for your window, which binds to a bool? property on your VM. When the VM property is set to something non-null, the attached property sets the Window's DialogResult, which will automatically close the window.
If you want you can try this different approach.
You can do this by associating the OK button with a command. Create an event such as LoginSuccess and when then add a window.Close() to the list of event callback. Then you have only to raise the LoginSuccess event to close the windows.
In my opinion, this respect the MVVM pattern defining an event that can be used for other trigger and not only for closing windows.
You could do this fairly easily by creating an attached property or Behavior (from Blend SDK) that hooked into your Window.
I posted a sample behavior to the Expression Code Gallery which does something similar (though definitely different) - it prevents a window from being closed via a property on the VM. You could very easily adapt the code (included in the download) to just close the window on a property change.

Update Dependency Property prior to program exit

I have created a dependency property of type Binary on a RichTextBox which allows me to bind to a FlowDocument which is in binary form (byte[]) within the ViewModel. This works well, the property converts to and back correctly.
Whenever the RichTextBox looses focus then the value of the dependency property is updated with the new binary representation of the FlowDocument.
My problem is that if I have been using the RichTextBox and I close the window, the RichTextBox does not lose focus and hence the dependency property is not updated with the new binary representation of the FlowDocument and therefore new changes are not commited to the database. In my ViewModel I have a method CleanUp which gets called when a ViewModel is getting ready to be disposed, where I can save the updated document.
How can I get the dependency property to update itself as the RichTextBox doesn't lose focus if the user clicks to close the window?
I brainstormed the following:
Tell the dependency property to update itself via a message broadcast. I am not clear on how to register a message listener within the dependency property.
Query the RichTextBox directly, get the Document, convert it to a binary object manually.
Get the view to move focus to a dummy control, so that the dependency property now updates itself.
What do you guys think?
Update: the on changed event for the dependency property adds a event handler which is waiting for the RichTextBox to loose focus. It is this handler that updates the dependency with its new value.
Use an UpdateSourceTrigger of "PropertyChanged"
Something like:
{Binding Path=MyProperty,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}
I had a similar problem once, the solution I used was to move the focus to a different control and I never had any problems with this.
In my case there were several editable controls in the window so I didn't have to use a dummy control.
What's stopping you from handling the closing/closed event of the Window and moving focus or updating the binding?

Can't set focus to a child of UserControl

I have a UserControl which contains a TextBox. When my main window loads I want to set the focus to this textbox so I added Focusable="True" GotFocus="UC_GotFocus" to the UserControls definition and FocusManager.FocusedElement="{Binding ElementName=login}" to my main windows definition. In the UC_GotFocus method i simply call .Focus() on the control i want to focus on but this doesn't work.
All i need to do is have a TextBox in a UserControl receive focus when the application starts.
Any help would be appreciated, thanks.
I recently fixed this problem for a login splash screen that is being displayed via a storyboard when the main window is first loaded.
I believe there were two keys to the fix. One was to make the containing element a focus scope. The other was to handle the Storyboard Completed event for the storyboard that was triggered by the window being loaded.
This storyboard makes the username and password canvas visible and then fades into being 100% opaque. The key is that the username control was not visible until the storyboard ran and therefore that control could not get keyboard focus until it was visible. What threw me off for awhile was that it had "focus" (i.e. focus was true, but as it turns out this was only logical focus) and I did not know that WPF had the concept of both logical and keyboard focus until reading Kent Boogaart's answer and looking at Microsoft's WPF link text
Once I did that the solution for my particular problem was straightforward:
1) Make the containing element a focus scope
<Canvas FocusManager.IsFocusScope="True" Visibility="Collapsed">
<TextBox x:Name="m_uxUsername" AcceptsTab="False" AcceptsReturn="False">
</TextBox>
</Canvas>
2) Attach a Completed Event Handler to the Storyboard
<Storyboard x:Key="Splash Screen" Completed="UserNamePassword_Storyboard_Completed">
...
</Storyboard>
and
3) Set my username TextBox to have the keyboard focus in the storyboard completed event handler.
void UserNamePassword_Storyboard_Completed(object sender, EventArgs e)
{
m_uxUsername.Focus();
}
Note that calling item.Focus() results in the call Keyboard.Focus(this), so you don't need to call this explicitly. See this question about the difference between Keyboard.Focus(item) and item.Focus.
Its stupid but it works:
Pop a thread that waits a while then comes back and sets the focus you want. It even works within the context of an element host.
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
System.Threading.ThreadPool.QueueUserWorkItem(
(a) =>
{
System.Threading.Thread.Sleep(100);
someUiElementThatWantsFocus.Dispatcher.Invoke(
new Action(() =>
{
someUiElementThatWantsFocus.Focus();
}));
}
);
}
Just recently I had a list-box that housed some TextBlocks. I wanted to be able to double click on the text block and have it turn into a TextBox, then focus on it and select all the text so the user could just start typing the new name (Akin to Adobe Layers)
Anyway, I was doing this with an event and it just wasn't working. The magic bullet for me here was making sure that I set the event to handled. I figure it was setting focus, but as soon as the event went down the path it was switching the logical focus.
The moral of the story is, make sure you're marking the event as handled, that might be your issue.
“When setting initial focus at application startup, the element to
receive focus must be connected to a PresentationSource and the
element must have Focusable and IsVisible set to true. The recommended
place to set initial focus is in the Loaded event handler"
(MSDN)
Simply add a "Loaded" event handler in the constructor of your Window (or Control), and in that event handler call the Focus() method on the target control.
public MyWindow() {
InitializeComponent();
this.Loaded += new RoutedEventHandler(MyWindow_Loaded);
}
void MyWindow_Loaded(object sender, RoutedEventArgs e) {
textBox.Focus();
}
since i tried a fuzquat's solution and found it the most generic one, i thought i'd share a different version, since some complained about it looking messy. so here it is:
casted.Dispatcher.BeginInvoke(new Action<UIElement>(x =>
{
x.Focus();
}), DispatcherPriority.ApplicationIdle, casted);
no Thread.Sleep, no ThreadPool. Clean enough i hope.
UPDATE:
Since people seem to like pretty code:
public static class WpfExtensions
{
public static void BeginInvoke<T>(this T element, Action<T> action, DispatcherPriority priority = DispatcherPriority.ApplicationIdle) where T : UIElement
{
element.Dispatcher.BeginInvoke(priority, action);
}
}
now you can call it like this:
child.BeginInvoke(d => d.Focus());
WPF supports two different flavors of focus:
Keyboard focus
Logical focus
The FocusedElement property gets or sets logical focus within a focus scope. I suspect your TextBox does have logical focus, but its containing focus scope is not the active focus scope. Ergo, it does not have keyboard focus.
So the question is, do you have multiple focus scopes in your visual tree?
I found a good series of blog posts on WPF focus.
Part 1: It’s Basically Focus
Part 2: Changing WPF focus in code
Part 3: Shifting focus to the first available element in WPF
They are all good to read, but the 3rd part specifically deals with setting focus to a UI element in a UserControl.
Set your user control to Focusable="True" (XAML)
Handle the GotFocus event on your control and call yourTextBox.Focus()
Handle the Loaded event on your window and call yourControl.Focus()
I have a sample app running with this solution as I type. If this does not work for you, there must be something specific to your app or environment that causes the problem. In your original question, I think the binding is causing the problem.
I hope this helps.
After having a 'WPF Initial Focus Nightmare' and based on some answers on stack, the following proved for me to be the best solution.
First, add your App.xaml OnStartup() the followings:
EventManager.RegisterClassHandler(typeof(Window), Window.LoadedEvent,
new RoutedEventHandler(WindowLoaded));
Then add the 'WindowLoaded' event also in App.xaml :
void WindowLoaded(object sender, RoutedEventArgs e)
{
var window = e.Source as Window;
System.Threading.Thread.Sleep(100);
window.Dispatcher.Invoke(
new Action(() =>
{
window.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
}));
}
The threading issue must be use as WPF initial focus mostly fails due to some framework race conditions.
I found the following solution best as it is used globally for the whole app.
Hope it helps...
Oran
I converted fuzquat's answer to an extension method. I'm using this instead of Focus() where Focus() did not work.
using System;
using System.Threading;
using System.Windows;
namespace YourProject.Extensions
{
public static class UIElementExtension
{
public static void WaitAndFocus(this UIElement element, int ms = 100)
{
ThreadPool.QueueUserWorkItem(f =>
{
Thread.Sleep(ms);
element.Dispatcher.Invoke(new Action(() =>
{
element.Focus();
}));
});
}
}
}
I've noticed a focus issue specifically related to hosting WPF UserControls within ElementHosts which are contained within a Form that is set as an MDI child via the MdiParent property.
I'm not sure if this is the same issue others are experiencing but you dig into the details by following the link below.
Issue with setting focus within a WPF UserControl hosted within an ElementHost in a WindowsForms child MDI form
I don't like solutions with setting another tab scope for UserControl. In that case, you will have two different carets when navigating by keyboard: on the window and the another - inside user control. My solution is simply to redirect focus from user control to inner child control. Set user control focusable (because by default its false):
<UserControl ..... Focusable="True">
and override focus events handlers in code-behind:
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
MyTextBox.Focus();
}
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnGotKeyboardFocus(e);
Keyboard.Focus(MyTextBox);
}
What did the trick for me was the FocusManager.FocusedElement attribute. I first tried to set it on the UserControl, but it didn't work.
So I tried putting it on the UserControl's first child instead:
<UserControl x:Class="WpfApplication3.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid FocusManager.FocusedElement="{Binding ElementName=MyTextBox, Mode=OneWay}">
<TextBox x:Name="MyTextBox"/>
</Grid>
... and it worked! :)
I have user control - stack panel with two text boxes.The text boxes were added in contructor, not in the xaml. When i try to focus first text box, nothing happend.
The siggestion with Loaded event fix my problem. Just called control.Focus() in Loaded event and everthing.
Assuming you want to set focus for Username textbox, thus user can type in directly every time it shows up.
In Constructor of your control:
this.Loaded += (sender, e) => Keyboard.Focus(txtUsername);
After trying combinations of the suggestions above, I was able to reliably assign focus to a desired text box on a child UserControl with the following. Basically, give focus to the child control and have the child UserControl give focus to its TextBox. The TextBox's focus statement returned true by itself, however did not yield the desired result until the UserControl was given focus as well. I should also note that the UserControl was unable to request focus for itself and had to be given by the Window.
For brevity I left out registering the Loaded events on the Window and UserControl.
Window
private void OnWindowLoaded(object sender, RoutedEventArgs e)
{
ControlXYZ.Focus();
}
UserControl
private void OnControlLoaded(object sender, RoutedEventArgs e)
{
TextBoxXYZ.Focus();
}
I set it in the PageLoaded() or control loaded, but then I'm calling WCF async service and doing stuff that seems to lose the focus. I have to to set it at the end of all the stuff I do. That's fine and all, but sometimes I make changes to the code and then I forget that I'm also setting the cursor.
I had same problem with setting keyboard focus to canvas in WPF user control.
My solution
In XAML set element to Focusable="True"
In element_mousemove event create simple check:
if(!element.IsKeyBoardFocused)
element.Focus();
In my case it works fine.

Resources