wpfxamlrichtextboxbehavioradorner

Mystery of Lost Focus - Unable to focus element from its attached behavior


I have struggled with this for days now--Google is of no help.

I have a RichTextBox being adorned with an InkCanvas. Strokes are collected and recognized from the InkCanvas. When the adorner is closed, the attached behavior on the RichTextBox is explicitly used to force focus back on the RichTextBox. However, from what I can tell, despite the explicit call to focus on the RichTextBox, the RichTextBox never receives the signal.

What's going on and how do I fix it?

TIA

XAML

      <ScrollViewer Grid.Row ="2" Grid.Column="1" VerticalScrollBarVisibility="Auto" >
            <!--ScrollViewer can only have one child.-->
            <Grid>

      ........................................

<!--
                    XAML that creates the adorned control and the adorner 
                -->
                <ac:AdornedControl IsAdornerVisible="{Binding TranscriptionLayer.IsAdornerVisible}" Grid.ColumnSpan="2">
                    <!--#region  Adorned Element-->
                    <!-- 
                        TranscriptionLayer will have an Adorner (inkcanvas) for handwriting recogntion.
                        The RichTextBox is the element being adorned and sets the size of the adornment object (i.e.,
                        the inkcanvas writing surface). The RichTextControl holds the actual transcript.
                    -->
                    <RichTextBox x:Name="RichTextControl" Panel.ZIndex="{Binding TranscriptionLayer.ZIndex}" 
                                 Height="{Binding VirtualPage.Height}" 
                                 Visibility="{Binding TranscriptionLayer.TranscriptIsVisible}"
                             SpellCheck.IsEnabled="True" 
                             VerticalScrollBarVisibility="Auto" 
                             AcceptsReturn="True" AcceptsTab="True"
                            >
                        <!--Remove blank line between paragraphs-->
                        <RichTextBox.Resources>
                            <Style TargetType="{x:Type Paragraph}">
                                <Setter Property="Margin" Value="0"/>
                            </Style>
                        </RichTextBox.Resources>

                        <i:Interaction.Behaviors>
                            <!--Update the menu and toolbar when a selection is made in the RichTextBox-->
                            <!--The behavior is bound to the SelectionChanged of the RichTextBox.
                            <b:RichTextBehavior AlignLeft ="{Binding TranscriptionLayer.AlignLeft}" /> -->
                            <b:RichTextBehavior 
                                FontHeight="{Binding ElementName=Fontheight, Path=SelectedItem, Mode=TwoWay, Converter={c:NullToDoubleConverter}}"
                                TextFont="{Binding ElementName=Fonttype,     Path=SelectedItem}"
                                TextBold="{Binding ElementName=ToggleBold,   Path=IsChecked}"
                                Italic="{Binding ElementName=ToggleItalic,   Path=IsChecked}"
                                Underline="{Binding ElementName=ToggleUnderline, Path=IsChecked}"
                                Strikethrough="{Binding ElementName=ToggleStrikethrough, Path=IsChecked}"
                                ParagraphTag ="{Binding ElementName=CurrentParagraph, Path=SelectedItem}" 
                                SelectedText="{Binding TranscriptionLayer.SelectedText}" 
                                IsFocused ="{Binding TranscriptionLayer.RichTextHasFocus}"
                            />
                        </i:Interaction.Behaviors>

                    </RichTextBox>


                    <!--#endregion-->
                    <ac:AdornedControl.AdornerContent>
                        <!--#region  The Adorner-->
                        <!-- This is the framework element as the adorner content. It is always on top of the adorned element. There adorned elements
                             is the RichTextConttrol.
                             The ItemsControl will expand to the width of the parent adorner which takes its size from the adorned element-the
                             RichTextConttrol. The ItemsControl inherits from  System.Windows.FrameworkElement. 
                        -->

                            <ItemsControl x:Name="WritingLayerControl" ItemsSource="{Binding TranscriptionLayer.WritingBoxes}" >
                                <!--
                                If the  <ItemsControl.ItemsPanel> is not used, the ItemsControl will default to a vertical StackPanel.
                            -->

                                <ItemsControl.ItemTemplate>
                                    <!--
                                DataTemplate and DataType point to a class, not a namespace!
                            -->
                                    <DataTemplate DataType="{x:Type vm:WritingBoxViewModel}" >
                                        <Grid>
                                            <Grid.RowDefinitions>
                                                <!-- 0 to be used for recogntion results-->
                                                <RowDefinition Height="auto" />
                                                <!-- 1 to be used for Ink -->
                                                <RowDefinition Height="auto"/>
                                            </Grid.RowDefinitions>

                                            <TextBlock Grid.Row="0" Text="{Binding RecognitionResults}" Background="#100000FF" 
                                                   Height="{Binding RecognitionResultsHeight}"/>

                                            <!--     
                                    Binding Mode must be set to TwoWay on TheSelectedStrokes because by default binding works one way, 
                                    i.e. loading changes from the view model, but not updating the viewmodel back. So either use:
                                    TheSelectedStrokes="{Binding SelectedStrokes, Mode=TwoWay}" or set it with
                                    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault in TheSelectedStrokes dependency definition.

                                    Background is of type Brush, so must use ImageBrush, DrawingBrush, VisualBrush.
                                    -->

                                            <InkCanvas x:Name="InkCanvas" Grid.Row="1" 
                                       Height="{Binding WritingBoxHeight}" Strokes="{Binding Strokes}"  
                                       EditingMode="{Binding EditingMode}"
                                       DefaultDrawingAttributes="{Binding DefaultDrawingAttributes}" >

                                                <i:Interaction.Behaviors>
                                                    <b:InkCanvasBehavior DeletedStrokes="{Binding DrawingLayer.DeleteStrokes}" />
                                                </i:Interaction.Behaviors>

                                                <InkCanvas.Background>
                                                    <!--
                                        Note: Making the DrawingBrush a StaticResource makes the XAML much more efficient as only one object
                                            is created. It also is only created on the first pass and no update from the Bindings will happen so
                                            the lines will not change even when the TypeSize is changed.

                                        Viewport (type Rect) gives the position, width, and height of the base tile
                                        -->
                                                    <DrawingBrush Stretch="Uniform" TileMode="Tile" Viewport="{Binding ViewPortBaseTile}" ViewportUnits="Absolute" >
                                                        <DrawingBrush.Drawing>
                                                            <GeometryDrawing>
                                                                <GeometryDrawing.Geometry>
                                                                    <GeometryGroup>
                                                                        <!--
                                                            X is horizontal displacement from origin.
                                                                Y is veritcal displacyement from origin.
                                                                Origin O(0,0) is top-left of InkCanvas.
                                                            -->

                                                                        <!-- Vertical Line
                                                                <LineGeometry StartPoint="0,0" EndPoint="{Binding TileBottom}"/>
                                                             -->
                                                                        <!-- Midline. Horizontal Line -->
                                                                        <LineGeometry StartPoint="{Binding MidLineStartPoint}" EndPoint="{Binding MidLineEndPoint}"/>

                                                                        <!-- BaseLine. Horizontal Line-->
                                                                        <LineGeometry StartPoint="{Binding BaseLineStartPoint}" EndPoint="{Binding BaseLineEndPoint}"/>
                                                                    </GeometryGroup>

                                                                </GeometryDrawing.Geometry>
                                                                <GeometryDrawing.Pen>
                                                                    <Pen Thickness="1" Brush="Tomato"/>
                                                                </GeometryDrawing.Pen>

                                                            </GeometryDrawing>
                                                        </DrawingBrush.Drawing>
                                                    </DrawingBrush>
                                                </InkCanvas.Background>
                                            </InkCanvas>

                                        </Grid>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>

                        <!--#endregion-->
                    </ac:AdornedControl.AdornerContent>
                </ac:AdornedControl>
            </Grid>
        </ScrollViewer> 

C#

namespace Behaviors
{
    public class RichTextBehavior : Behavior<RichTextBox>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.SelectionChanged += RichTextBoxSelectionChanged;
            AssociatedObject.TextChanged += RichTextBoxTextChanged;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.SelectionChanged -= RichTextBoxSelectionChanged;
            AssociatedObject.TextChanged -= RichTextBoxTextChanged;
        }

....................................................

public static bool GetIsFocused(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsFocusedProperty);
        }

        public static void SetIsFocused(DependencyObject obj, bool value)
        {
            obj.SetValue(IsFocusedProperty, value);
        }

        public static readonly DependencyProperty IsFocusedProperty =
            DependencyProperty.RegisterAttached(
                "IsFocused", typeof(bool), typeof(RichTextBehavior),
                new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));

        private static void OnIsFocusedPropertyChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var behavior = d as RichTextBehavior;

            var uie = (UIElement)behavior.AssociatedObject;
            if ((bool)e.NewValue)
            {
                uie.Focus(); // Don't care about false values.
            }
        }

The last line, uie.Focus() correctly resolves to the RichTextBoxControl --but does not reach the actual control!

This is demonstrated by the code-behind "GotFocus()" event never being called:

Code-Behind

 public partial class ProgressNotesView : UserControl
    {
        public ProgressNotesView()
        {
            InitializeComponent();
            RichTextControl.GotFocus += RichTextControl_GotFocus;
            RichTextControl.LostFocus += RichTextControl_LostFocus;

            Loaded += (s, e) =>
            {
                // When used as a UserControl as Data first, the DataContext has already been set to the ViewModel
                // before the UserControl is Initialized.
                vm = (ProgressNoteViewModel)DataContext;
                Fontheight.SelectedItem = 12.0;
            };
        }

        private void RichTextControl_LostFocus(object sender, RoutedEventArgs e)
        {
            var y = 10;
        }

        private void RichTextControl_GotFocus(object sender, RoutedEventArgs e)
        {
            var x = 10;
        }

So what magic is needed to return focus to the RichTextControl when I am finished using the InkCanvas in the adorner?

I would really very much appreciate any help with this (before I go completely bald)!

TIA


Solution

  • I tried using Snoop 2.8.0 without much luck (I could not find the focus events). However, placing

    UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
    

    at various points arround the uie.Focus() call quickly showed that the ScrollViewer was receiving the Focus, not the RichTextBox. Further study quickly showed that at the time uie.Focus() was called, the RichTextBox Visibility had been set to Hidden.

    Making sure the RichTextBox visibility was set to Visible before calling uie.Focus() fixed all issues.

    Lesson learned: Apparently an element must be visible before it can receive focus!