如何更改 FlowDocumentReader 的 FindTextBox 样式以匹配我的应用程序的主题?

How do I change the style of FindTextBox of FlowDocumentReader to match the theme of my app?

我有一个带有 FlowDocumentReader 控件的 WPF 应用程序,当我单击搜索按钮搜索文本时,文本框与应用程序的主题不匹配。文本有,但文本框的背景没有。因此,如果我使用深色主题,我在文本框中输入的文本是白色的(这是正确的),但文本框背景也是白色的(不正确,使文本难以辨认)。

有谁知道如何解决这个问题?我尝试应用样式,但我不知道要定位哪个组件。

哇,微软 真的 没让这一切变得简单。


我的过程

我尝试了将 Style TargeType="TextBox" 添加到 FlowDocumentReader.Resources 的简单技巧,但这不起作用。

我尝试以“正确”的方式做事并覆盖 FlowDocumentReaderControlTemplate,但有问题的 TextBox 甚至不是 ControlTemaplte 的一部分!相反,有一个名为 PART_FindToolBarHostBorder。我们想要的 TextBox 在代码中作为子项添加到 PART_FindToolBarHost - 但仅在 之后 用户单击“查找”按钮(带放大镜的按钮)玻璃图标)。您可以通过查看控件的 source code.

来亲自了解这一点

由于没有更多 XAML 的想法,我不得不求助于使用代码。我们需要以某种方式获得对正在创建的 TextBox 的引用,我想不出比手动搜索可视化树更好的方法了。由于 TextBox 仅在执行 find 命令后才存在,所以这很复杂。

具体来说,FlowDocumentReader 中的查找按钮绑定到 ApplicationCommands.Find。我尝试将该命令的 CommandBinding 添加到 FlowDocumentReader,这样我就可以将其用作检索 TextBox 的触发器。不幸的是,添加这样的 CommandBinding 以某种方式破坏了内置功能并阻止了 TextBox 的生成。即使您设置 e.Handled = False.

也会中断

幸运的是,FlowDocumentReader 公开了一个 OnFindCommand- except, of course, it's a Protected method. So I finally gave in and decided to inherit FlowDocumentReader. OnFindCommand works as a reliable trigger, but it turns out the TextBox isn't created until after the sub finishes. I was forced to use Dispatcher.BeginInvoke,以便在实际添加 TextBox 之后为 运行 安排一个方法。

使用OnFindCommand作为触发器,我终于能够可靠地获得对“查找”TextBox的引用,它实际上被命名为FindTextBox.

现在我可以获得参考,我们可以应用我们自己的 Style。除了:FindTextBox 已经有一个 Style,所以除非我们想覆盖它,否则我们将不得不合并两个 Style。对此没有可公开访问的方法(尽管 WPF 在某些地方在内部执行此操作),但幸运的是我已经有了一些代码。


工作代码

首先,Module 和我使用的辅助方法:

FindVisualChild 用于循环遍历可视化树并获取对 FindTextBox.
的引用 MergeStyles 用于将现有的 Style 与我们提供的 Style 结合起来,一旦我们有了该参考。

Module OtherMethods
    <Extension()>
    Public Function FindVisualChild(obj As DependencyObject, Name As String) As FrameworkElement
        For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(obj) - 1
            Dim ChildObj As DependencyObject = VisualTreeHelper.GetChild(obj, i)

            If TypeOf ChildObj Is FrameworkElement AndAlso DirectCast(ChildObj, FrameworkElement).Name = Name Then Return ChildObj

            ChildObj = FindVisualChild(ChildObj, Name)
            If ChildObj IsNot Nothing Then Return ChildObj
        Next
        Return Nothing
    End Function

    Public Function MergeStyles(ByVal style1 As Style, ByVal style2 As Style) As Style
        Dim R As New Style

        If style1 Is Nothing Then Throw New ArgumentNullException("style1")
        If style2 Is Nothing Then Throw New ArgumentNullException("style2")
        If style2.BasedOn IsNot Nothing Then style1 = MergeStyles(style1, style2.BasedOn)

        For Each currentSetter As SetterBase In style1.Setters
            R.Setters.Add(currentSetter)
        Next

        For Each currentTrigger As TriggerBase In style1.Triggers
            R.Triggers.Add(currentTrigger)
        Next

        For Each key As Object In style1.Resources.Keys
            R.Resources(key) = style1.Resources(key)
        Next

        For Each currentSetter As SetterBase In style2.Setters
            R.Setters.Add(currentSetter)
        Next

        For Each currentTrigger As TriggerBase In style2.Triggers
            R.Triggers.Add(currentTrigger)
        Next

        For Each key As Object In style2.Resources.Keys
            R.Resources(key) = style2.Resources(key)
        Next

        Return R
    End Function
End Module

然后是StyleableFlowDocumentReader,也就是我给继承FlowDocumentReader的扩展控件命名的:

Public Class StyleableFlowDocumentReader
    Inherits FlowDocumentReader

    Protected Overrides Sub OnFindCommand()
        MyBase.OnFindCommand()
        Dispatcher.BeginInvoke(Sub() GetFindTextBox(), DispatcherPriority.Render)
    End Sub

    Private Sub GetFindTextBox()
        findTextBox = Me.FindVisualChild("FindTextBox")
        ApplyFindTextBoxStyle()
    End Sub

    Private Sub ApplyFindTextBoxStyle()
        If findTextBox IsNot Nothing Then
            If findTextBox.Style IsNot Nothing AndAlso FindTextBoxStyle IsNot Nothing Then
                findTextBox.Style = MergeStyles(findTextBox.Style, FindTextBoxStyle)
            Else
                findTextBox.Style = If(FindTextBoxStyle, findTextBox.Style)
            End If
        End If
    End Sub

    Private findTextBox As TextBox

    Public Property FindTextBoxStyle As Style
        Get
            Return GetValue(FindTextBoxStyleProperty)
        End Get
        Set(ByVal value As Style)
            SetValue(FindTextBoxStyleProperty, value)
        End Set
    End Property
    Public Shared ReadOnly FindTextBoxStyleProperty As DependencyProperty =
                           DependencyProperty.Register("FindTextBoxStyle",
                           GetType(Style), GetType(StyleableFlowDocumentReader),
                           New PropertyMetadata(Nothing, Sub(d, e) DirectCast(d, StyleableFlowDocumentReader).ApplyFindTextBoxStyle()))
End Class

然后,最后,一个用法示例:

<local:StyleableFlowDocumentReader x:Name="Reader">
    <local:StyleableFlowDocumentReader.FindTextBoxStyle>
        <Style TargetType="TextBox">
            <Setter Property="Foreground" Value="Blue"/>
        </Style>
    </local:StyleableFlowDocumentReader.FindTextBoxStyle>
        
    <FlowDocument/>
</local:StyleableFlowDocumentReader>