2

I attempted to follow a pattern described in an answer to this question. How to set focus to textbox using MVVM?

However, I am having trouble with the concept of keyboard focus. If I have notepad or some other application running at the same time as my WPF application and click on notepad to put the keyboard focus there, then do something to cause my other application to put the focus into one of its text boxes, then the trigger gives the visual cue that my application's text box now has the keyboard focus. However when I start typing I can see that is not the case because the text is actually going into notepad.

Here is the xaml for my trigger.

<TextBox.Style>
    <Style TargetType="{x:Type TextBox}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding ReadyForDataEntry}" Value="True">
                <Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}" />
           </DataTrigger>
            <Trigger Property="IsKeyboardFocused" Value="true">
                <Setter Property="Background" Value="Lavender"/>
                <Setter Property="BorderBrush" Value="Blue"/>
            </Trigger>
        </Style.Triggers>
 </Style>

Essentially the textbox will sometimes light up with the border and background color indicating that IsKeyboardFocused = true for that textbox, even though keyboard entry will be received by whatever application (e.g., one note, notepad) was last clicked in. What am I missing? Why would that WPF control have IsKeyboardFocused set true when the keyboard focus is clearly not true at all?

shawn1874
  • 1,288
  • 1
  • 10
  • 26
  • complete minimum example plz. Your example works perfectly fine on my machine – Steve Jan 08 '18 at 20:31
  • What do you mean? Did you do exactly what I stated? – shawn1874 Jan 08 '18 at 20:42
  • yes. clicking into notepad would turn the background back to white(default). Your DataTrigger might actually be where the problem is at. A complete minimum example always helps – Steve Jan 08 '18 at 20:49
  • I'm working on it. Clicking into notepad initially isn't the problem. The problem is when a service triggers an event to put the focus back into the control after the keyboard focus was lost to notepad. The problem is that the keyboard focus is not really going back into the control even though the trigger lights it up. I have to code something with a background service that will duplicate the problem and then I'll post a github link to it. It will take a little while to create a minimal example but I'll edit the question with a link when I have one. – shawn1874 Jan 08 '18 at 21:06
  • I included a minimal example in my answer that reliably reproduces the issue on Windows 7. This is an old, known issue (well, known to me at least). – Mike Strobel Jan 08 '18 at 21:22
  • Ok. I had just posted a github sample doing essentially the same thing that ou did Mike. I'm glad you understood the issue but is there no way for my application to make itself active? https://github.com/Shawn1874/CodeSamples/tree/master/WpfSamples/SetFocusInTextBoxProblem – shawn1874 Jan 08 '18 at 21:59

1 Answers1

2

You're not doing anything wrong; this is a known quirk of WPF.

When a control receives logical focus, WPF attempts to give it keyboard focus as well. However, when you assign keyboard focus in an inactive WPF application, the application behaves as if it's active. That means, among other things, a focused TextBox will show a blinking caret, and IsKeyboardFocused and related properties will be set.

I have seen this issue in the past, and it was trivial to reproduce.

Xaml:

<Window x:Class="WpfTest.FocusTest"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>
    <StackPanel HorizontalAlignment="Center"
                VerticalAlignment="Center">
      <TextBox x:Name="_textBox"
               Width="150">
        <TextBox.Style>
          <Style TargetType="{x:Type TextBox}">
            <Style.Triggers>
              <Trigger Property="IsKeyboardFocused"
                       Value="true">
                <Setter Property="Background"
                        Value="Lavender" />
                <Setter Property="BorderBrush"
                        Value="Blue" />
              </Trigger>
            </Style.Triggers>
          </Style>
        </TextBox.Style>
      </TextBox>
      <Button Margin="0,7,0,0"
              Content="_Click Me"
              Click="OnButtonClick" />
    </StackPanel>
  </Grid>
</Window>

Code behind:

public partial class FocusTest
{
    public FocusTest()
    {
        InitializeComponent();
    }

    private void OnButtonClick(object sender, RoutedEventArgs e)
    {
        _textBox.Text = "";

        // NOTE: Requires System.Reactive.Core, available in NuGet.
        System.Reactive.Concurrency.Scheduler.Default.Schedule(
            TimeSpan.FromSeconds(5),
            () => this.Dispatcher.BeginInvoke(new Action(this.SetFocus)));
    }

    private void SetFocus()
    {
        _textBox.Text = "Focused";
        FocusManager.SetFocusedElement(_textBox, _textBox);
    }
}

Hit the button, Alt+Tab over to Notepad, and wait 5 seconds for the TextBox to receive focus. The IsKeyboardFocused trigger is fired, and the blinking caret appears, but keyboard input is still sent to Notepad.

The key point here is that the problem only arises when an element is given focus while another application is active (hence the artificial delay). Note that the problem still arises if you replace the SetFocusedElement call with Keyboard.Focus(_textBox), _textBox.Focus(), and other variations.

Unfortunately, I am not aware of a reliable, non-hacky way of fixing this issue. I don't recall how much time I spent on it, but I ultimately decided it wasn't worth the trouble. It's just not something that comes up that often.

Mike Strobel
  • 25,075
  • 57
  • 69
  • When you state non-hacky way of fixing this issue, what do you mean exactly? By fix would you mean make it so that the IsKeyboardFocused remains false when the other application is active or is there a way to also make my own application the active one? Perhaps the real issue here is that another application is active but but I want mine to be activated. On the other hand, maybe what I want isn't an appropriate design. – shawn1874 Jan 08 '18 at 22:05
  • @shawn1874 I only meant that I'd spent time trying to 'fix' the issue, but I did not come up with an acceptable solution. I don't remember whether I found a hacky solution or failed to find a solution at all. The 'fix' would have been "fix it so the app doesn't behave like it has keyboard focus when it really doesn't". I don't think you can get the behavior you want. Since Windows Vista/7 at least, your application can only change the foreground window when (1) your application is active; or (2) the active application has given your app permission to change the foreground window. – Mike Strobel Jan 08 '18 at 22:23
  • Ok. I found a way to set my application window as the active window for the use case in question. Since I only do this one time after the discovery of a device it accomplishes what I need and prevents this weird issue from happening. I am targeting windows 7, but it sounds like you are suggesting that this wouldn't work for an application running on windows 8 or 10. – shawn1874 Jan 08 '18 at 22:26
  • @shawn1874 My understanding is that it shouldn't work on Windows 7 either. As far as I know, you can only set the foreground window (1) from the currently active application, or (2) when the currently active application has given you permission to do so, e.g., by calling the Win32 method `AllowSetForegroundWindow`. Maybe I'm mistaken, but when I had separate application and launcher processes, I could only get the application to bring itself into the foreground if the launcher process explicitly gave it permission to do so. But, hey, if you got it working, that's great :). – Mike Strobel Jan 08 '18 at 22:31
  • @shawn1874 Also, make sure you test with *and* without the debugger attached. – Mike Strobel Jan 08 '18 at 22:35