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?

Original source