Question

I am writing a wpf application and I am trying to add F1 help support to it.

I found this very usefull class by Nigel Shaw

I wrote a test help chm file with the Microsoft HTML Help Workshop.

I integrated them to my application. I set the HelpTopic for my main Window, a custom control (CC1) I add dynamically to the Main Window, and to another custom control (CC2) I add dynamically to CC1.

When I press F1 in the Main window, I get the correct help subject to open. When I press F1 in CC1, I get the correct help subject to open. When I press F1 in CC2, I get the help subject of CC1.

I added some code to get the stack of controls when the GetHelpTopic function is called and this is what I get ([0] being the control that catch the F1):

[0] System.Windows.Controls.ScrollViewer
[1] System.Windows.Controls.Grid
[2] System.Windows.Controls.Grid
[3] System.Windows.Controls.Grid
[4] CC1
[5] System.Windows.Controls.Canvas
[6] System.Windows.Controls.ScrollViewer
[7] System.Windows.Controls.Grid
[8] System.Windows.Controls.Grid
[9] CustomPanel
[10] System.Windows.Controls.TabItem
[11] System.Windows.Controls.TabControl
[12] System.Windows.Controls.Grid
[13] MainWindow

At first I thought that maybe the ScrollViewer catches the F1 and prevents it to go deeper. But then I would have gotten the stack starting at [6] instead.

Then I thought that maybe the problem came from a difference between CC1 and CC2 classes. But they both inherit from the same base class which inherit from UserControl

UserControl - UserControlXY - AnimatedControl - AnimatedControlValidated - CC1

UserControl - UserControlXY - AnimatedControl - AnimatedControlValidated - AnimatedStructure - CC2

Update 1: I am getting closer. If I click inside a control in CC2, then I get the following stack

[0] System.Windows.Controls.TextBox
[1] System.Windows.Controls.Grid
[2] System.Windows.Controls.Grid
[3] CC2
[4] System.Windows.Controls.Canvas
[5] System.Windows.Controls.ScrollViewer
[6] System.Windows.Controls.Grid
[7] System.Windows.Controls.Grid
[8] System.Windows.Controls.Grid
[9] CC1
[10] System.Windows.Controls.Canvas
[11] System.Windows.Controls.ScrollViewer
[12] System.Windows.Controls.Grid
[13] System.Windows.Controls.Grid
[14] CustomPanel
[15] System.Windows.Controls.TabItem
[16] System.Windows.Controls.TabControl
[17] System.Windows.Controls.Grid
[18] MainWindow

And I get the correct help topic for CC2. So I am guessing it's a problem of setting the focus on CC2 when I click on it.

So I added the following tag to CC2:

Focusable="True"

But in that case I still get the previously wrong behavior when I click on CC2 background or elements that not focusable (ex: labels)...

So next I added a MouseLeftButtonDown to set the focus manually

MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(AnimatedStructure_MouseLeftButtonDown);

With the event doing this:

private void AnimatedStructure_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
   this.Focus();
}

But even with this I am still getting the previously wrong help topic...


Update 2:

This time I added to CC2

GotFocus += new System.Windows.RoutedEventHandler(AnimatedStructure_GotFocus);
LostFocus += new System.Windows.RoutedEventHandler(AnimatedStructure_LostFocus);

I also modified AnimatedStructure_MouseLeftButtonDown to use the FocusManager like this:

FocusManager.SetFocusedElement(this.Parent, this);

I put a breakpoint in GotFocus and LostFocus. When I click inside CC2, GotFocus is fired properly by the FocusManager from AnimatedStructure_MouseLeftButtonDown BUT, immediately after that I receive a LostFocus from CC2 itself. I looked at the RoutedEventArgs and it is really CC2 itself that removes its own focus.

So now I am a bit lost about what to do... Because of that I cannot

Was it helpful?

Solution

Finally found the problem and the solution in one simple blog entry

Thank you who ever you are who wrote that blog entry.

Short answer: WPF Focus mechanism is badly broken.

For the long answer, simply read the blog as the writer gives details on why the behavior I experienced happens.

I addedf the following class to my solution

static class FocusHelper
{
    private delegate void MethodInvoker();

    public static void Focus(UIElement element)
    {
        //Focus in a callback to run on another thread, ensuring the main UI thread is initialized by the
        //time focus is set
        ThreadPool.QueueUserWorkItem(delegate(Object foo) {
            UIElement elem = (UIElement)foo;
            elem.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                (MethodInvoker)delegate()
            {
                elem.Focus();
                Keyboard.Focus(elem);
            });
        }, element);
    }
}

And this in CC2

MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(AnimatedStructure_MouseLeftButtonDown);

private void AnimatedStructure_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    FocusHelper.Focus(this);
}

That did the trick. Now the focus is properly set and maintained on CC2 and I get the correct HelpTopic

Hurray!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top