Detect focus lost from a group of WPF controls
c#, f#, focus, wpf
Solution
Thanks to Ian Voyce on Twitter I was able to get the functionality I needed using the `IsKeyboardFocusWithinChanged` event. Here's a demo:
open System.Windows
let Button x =
Controls.Button(Content=x, Width=64.0, Margin=Thickness 3.0)
let Panel ctrls =
let panel = Controls.StackPanel()
for ctrl in ctrls do
panel.Children.Add ctrl
|> ignore
panel
let label = Controls.Label(Content="Edit 12")
let button1 = Button "1"
let button2 = Button "2"
let button3 = Button "3"
let panel = Panel[button1; button2]
[<System.STAThreadAttribute>]
do
label.HorizontalContentAlignment <- HorizontalAlignment.Center
label.MouseLeftButtonDown.Add(fun _ ->
label.Content <- panel)
panel.IsKeyboardFocusWithinChanged.Add(fun e ->
if not(unbox e.NewValue) then
label.Content <- "Edit 12")
Application().Run(Window(Content=Panel[label :> UIElement; button3 :> UIElement]))
|> ignore
Problem
I'm building a GUI using code that has editable contents. When the user clicks on the static display the control is swapped out for one that allows editing. In some cases, the display control (e.g. `Label`) is swapped out for a group of controls such as a `ComboBox` and `TextBox`. I want to detect when focus is lost from my group of editable controls in order to switch the interface back from the editor to the display representation for that item. For example, I might have a GUI in a tree like `Panel1(Panel2(Button1, Button2), Button3)` and I would like to detect when focus is lost from `Panel2`. I tried the following (F# code): ``` open System.Windows let button1 = Controls.Button(Content="1") let button2 = Controls.Button(Content="2") let button3 = Controls.Button(Content="3") [<System.STAThreadAttribute>] do let panel1 = Controls.StackPanel() let panel2 = Controls.StackPanel() panel2.Children.Add button1 |> ignore panel2.Children.Add button2 |> ignore panel1.Children.Add panel2 |> ignore panel1.Children.Add button3 |> ignore panel2.LostFocus.Add(fun _ -> printfn "Panel2 lost focus") Application().Run(Window(Content=panel1)) |> ignore ``` The `panel2.LostFocus` event is triggered when `button3` is clicked after `button2` had been clicked, as expected, because the focus as moved out of `panel1` to `button3`. However, it is also triggered when `button2` is clicked after `button1` had been clicked even though `panel2` never lost focus. Reading the MSDN documentation about focus in WPF I tried adding: ``` Input.FocusManager.SetIsFocusScope(panel2, true) ``` but this actually made the problem worse! Now the `panel2.LostFocus` event is triggered only when focus shift from one child of `panel2` to another and not when `panel2` actually loses focus. How should I get the desired effect?