失去焦点之谜 - 无法从其附加行为中聚焦元素
Mystery of Lost Focus - Unable to focus element from its attached behavior
我已经为此苦苦挣扎了好几天--Google 没有帮助。
我有一个用 InkCanvas 装饰的 RichTextBox。从 InkCanvas 收集和识别笔划。当装饰器关闭时,RichTextBox 上的附加行为被显式用于强制焦点回到 RichTextBox 上。然而,据我所知,尽管明确调用了 RichTextBox,但 RichTextBox 从未收到信号。
这是怎么回事,我该如何解决?
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.
}
}
最后一行,uie.Focus() 正确解析为 RichTextBoxControl -- 但未到达实际控件!
这由从未被调用的代码隐藏 "GotFocus()" 事件证明:
代码隐藏
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;
}
那么当我在装饰器中使用完 InkCanvas 后,需要什么魔法才能return 聚焦到 RichTextControl?
我真的非常感谢任何对此的帮助(在我完全秃顶之前)!
TIA
我尝试使用 Snoop 2.8.0 但运气不佳(我找不到焦点事件)。然而,放置
UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
在 uie.Focus() 调用周围的不同位置,快速显示 ScrollViewer 正在接收焦点,而不是 RichTextBox。进一步的研究很快表明,在调用 uie.Focus() 时,RichTextBox 可见性已设置为隐藏。
确保在调用 uie.Focus() 之前将 RichTextBox 可见性设置为 Visible 解决了所有问题。
经验教训:显然,元素必须可见才能获得焦点!
我已经为此苦苦挣扎了好几天--Google 没有帮助。
我有一个用 InkCanvas 装饰的 RichTextBox。从 InkCanvas 收集和识别笔划。当装饰器关闭时,RichTextBox 上的附加行为被显式用于强制焦点回到 RichTextBox 上。然而,据我所知,尽管明确调用了 RichTextBox,但 RichTextBox 从未收到信号。
这是怎么回事,我该如何解决?
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.
}
}
最后一行,uie.Focus() 正确解析为 RichTextBoxControl -- 但未到达实际控件!
这由从未被调用的代码隐藏 "GotFocus()" 事件证明:
代码隐藏
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;
}
那么当我在装饰器中使用完 InkCanvas 后,需要什么魔法才能return 聚焦到 RichTextControl?
我真的非常感谢任何对此的帮助(在我完全秃顶之前)!
TIA
我尝试使用 Snoop 2.8.0 但运气不佳(我找不到焦点事件)。然而,放置
UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
在 uie.Focus() 调用周围的不同位置,快速显示 ScrollViewer 正在接收焦点,而不是 RichTextBox。进一步的研究很快表明,在调用 uie.Focus() 时,RichTextBox 可见性已设置为隐藏。
确保在调用 uie.Focus() 之前将 RichTextBox 可见性设置为 Visible 解决了所有问题。
经验教训:显然,元素必须可见才能获得焦点!