搜索文本框控件(改编自另一个控件)无法触发文本更改,也无法设置或获取文本 WPF

Search TextBox Control (adapted from another control) cannot fire textchanged, nor can I set or get text WPF

这是我的搜索文本框 class,XAML 设计中的实际文本框是 PART_TextBox,我尝试使用 PART_TextBox_TextChanged,TextBox_TextChanged,和 OnTextBox_TextChanged,none 工作,.text 总是空的。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace SearchTextBox
{


[TemplatePart(Name = PartRootPanelName, Type = typeof(Panel))]
[TemplatePart(Name = PartTextBoxName, Type = typeof(TextBox))]
[TemplatePart(Name = PartSearchIconName, Type = typeof(Button))]
[TemplatePart(Name = PartCloseButtonName, Type = typeof(Button))]
public class SearchTextBox : Control
{

    private const string PartRootPanelName = "PART_RootPanel";
    private const string PartTextBoxName = "PART_TextBox";
    private const string PartSearchIconName = "PART_SearchIcon";
    private const string PartCloseButtonName = "PART_CloseButton";


    // Commands.

    public static readonly RoutedCommand ActivateSearchCommand;
    public static readonly RoutedCommand CancelSearchCommand;


    // Properties.

    public static readonly DependencyProperty HandleClickOutsidesProperty;
    public static readonly DependencyProperty UpdateDelayMillisProperty;
    public static readonly DependencyProperty HintTextProperty;
    public static readonly DependencyProperty DefaultFocusedElementProperty;
    public static readonly DependencyProperty TextBoxTextProperty;
    public static readonly DependencyProperty TextBoxTextChangedProperty;



    static SearchTextBox()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchTextBox), new FrameworkPropertyMetadata(typeof(SearchTextBox)));

        ActivateSearchCommand = new RoutedCommand();
        CancelSearchCommand = new RoutedCommand();


        // // Using of CommandManager.
        // // https://www.codeproject.com/Articles/43295/ZoomBoxPanel-add-custom-commands-to-a-WPF-control
        CommandManager.RegisterClassCommandBinding(typeof(SearchTextBox),
            new CommandBinding(ActivateSearchCommand, ActivateSearchCommand_Invoke));

        CommandManager.RegisterClassCommandBinding(typeof(SearchTextBox),
            new CommandBinding(CancelSearchCommand, CancelSearchCommand_Invoke));


        // Register properties.

        HandleClickOutsidesProperty = DependencyProperty.Register(
            nameof(HandleClickOutsides), typeof(bool), typeof(SearchTextBox),
            new UIPropertyMetadata(true));
        // // Set default value.
        // // 

        UpdateDelayMillisProperty = DependencyProperty.Register(
            nameof(UpdateDelayMillis), typeof(int), typeof(SearchTextBox),
            new UIPropertyMetadata(1000));

        HintTextProperty = DependencyProperty.Register(
        nameof(HintText), typeof(string), typeof(SearchTextBox),
        new UIPropertyMetadata("Set HintText property"));

        DefaultFocusedElementProperty = DependencyProperty.Register(
            nameof(DefaultFocusedElement), typeof(UIElement), typeof(SearchTextBox));

        TextBoxTextProperty =
            DependencyProperty.Register(
                "Text",
                typeof(string),
                typeof(SearchTextBox));

       

    }

    public string Text 
    {
        get { return (string)GetValue(TextBoxTextProperty);  }
        set { SetValue(TextBoxTextProperty, value);  }
    }

    public static void ActivateSearchCommand_Invoke(object sender, ExecutedRoutedEventArgs e)
    {
        if (sender is SearchTextBox self)
            self.ActivateSearch();
    }


    public static void CancelSearchCommand_Invoke(object sender, ExecutedRoutedEventArgs e)
    {
        if (sender is SearchTextBox self)
        {
            self.textBox.Text = "";
            self.CancelPreviousSearchFilterUpdateTask();
            self.UpdateFilterText();
            self.DeactivateSearch();
        }
    }


    private static UIElement GetFirstSelectedControl(Selector list)
        => list.SelectedItem == null ? null :
            list.ItemContainerGenerator.ContainerFromItem(list.SelectedItem) as UIElement;


    private static UIElement GetDefaultSelectedControl(Selector list)
        => list.ItemContainerGenerator.ContainerFromIndex(0) as UIElement;


    // Events.

    // // Using of events.
    // // 
    public event EventHandler SearchTextFocused;
    public event EventHandler SearchTextUnfocused;
    // // Parameter passing:
    // // 
    public event EventHandler<string> SearchRequested;

    public event TextChangedEventHandler TextChanged;



    

    // Parts.

    private Panel rootPanel;
    private TextBox textBox;
    private Button searchIcon;
    private Label closeButton;


    // Handlers.

    // Field for click-outsides handling.
    private readonly MouseButtonEventHandler windowWideMouseButtonEventHandler;


    // Other fields.

    private CancellationTokenSource waitingSearchUpdateTaskCancellationTokenSource;


    // <init>

    public SearchTextBox()
    {
        // Click events in the window will be previewed by
        // function OnWindowWideMouseEvent (defined later)
        // when the handler is on. Now it's off.
        windowWideMouseButtonEventHandler =
            new MouseButtonEventHandler(OnWindowWideMouseEvent);

       
    }

   

    // Properties.
    

    public bool HandleClickOutsides
    {
        get => (bool)GetValue(HandleClickOutsidesProperty);
        set => SetValue(HandleClickOutsidesProperty, value);
    }

    public int UpdateDelayMillis
    {
        get => (int)GetValue(UpdateDelayMillisProperty);
        set => SetValue(UpdateDelayMillisProperty, value);
    }

    public string HintText
    {
        get => (string)GetValue(HintTextProperty);
        set => SetValue(HintTextProperty, value);
    }


    public UIElement DefaultFocusedElement
    {
        get => (UIElement)GetValue(DefaultFocusedElementProperty);
        set => SetValue(DefaultFocusedElementProperty, value);
    }


    //Event handler functions.


    // This would only be on whenever search box is focused.
    private void OnWindowWideMouseEvent(object sender, MouseButtonEventArgs e)
    {
        // By clicking outsides the search box deactivate the search box.
        if (!IsMouseOver) DeactivateSearch();
    }


    private void PART_TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        TextChanged?.Invoke(this, e);
    }


    public void OnTextBox_GotFocus(object sender, RoutedEventArgs e)
    {
        SearchTextFocused?.Invoke(this, e);
        if (HandleClickOutsides)
            // Get window.
            // 
            Window.GetWindow(this).AddHandler(
                Window.PreviewMouseDownEvent, windowWideMouseButtonEventHandler);
    }


    public void OnTextBox_LostFocus(object sender, RoutedEventArgs e)
    {
        SearchTextUnfocused?.Invoke(this, e);
        if (HandleClickOutsides)
            Window.GetWindow(this).RemoveHandler(
                Window.PreviewMouseDownEvent, windowWideMouseButtonEventHandler);
    }


    private void OnTextBox_KeyUp(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Escape)
            CancelSearchCommand.Execute(null, this);
        else if (e.Key == Key.Enter)
        {
            CancelPreviousSearchFilterUpdateTask();
            UpdateFilterText();
        }
        else
        {
            CancelPreviousSearchFilterUpdateTask();
            // delay == 0: Update now;
            // delay < 0: Don't update except Enter or Esc;
            // delay > 0: Delay and update.
            var delay = UpdateDelayMillis;
            if (delay == 0) UpdateFilterText();
            else if (delay > 0)
            {
                // // Delayed task.
                // // 
                waitingSearchUpdateTaskCancellationTokenSource = new CancellationTokenSource();
                Task.Delay(delay, waitingSearchUpdateTaskCancellationTokenSource.Token)
                   .ContinueWith(self => {
                       if (!self.IsCanceled) Dispatcher.Invoke(() => UpdateFilterText());
                   });
            }
        }
    }


    // Public interface.


    public void ActivateSearch()
    {
        textBox?.Focus();
    }

    public void DeactivateSearch()
    {
        // // Use keyboard focus instead.
        // // 
        //Keyboard.ClearFocus();
        if (DefaultFocusedElement != null)
        {
            UIElement focusee = null;
            if (DefaultFocusedElement is Selector list)
            {
                focusee = GetFirstSelectedControl(list);
                if (focusee == null)
                    focusee = GetDefaultSelectedControl(list);
            }
            if (focusee == null) focusee = DefaultFocusedElement;
            Keyboard.Focus(focusee);
        }
        else
        {
            rootPanel.Focusable = true;
            Keyboard.Focus(rootPanel);
            rootPanel.Focusable = false;
        }
    }


    // Helper functions.


    private void CancelPreviousSearchFilterUpdateTask()
    {
        if (waitingSearchUpdateTaskCancellationTokenSource != null)
        {
            waitingSearchUpdateTaskCancellationTokenSource.Cancel();
            waitingSearchUpdateTaskCancellationTokenSource.Dispose();
            waitingSearchUpdateTaskCancellationTokenSource = null;
        }
    }


    private void UpdateFilterText() => SearchRequested?.Invoke(this, textBox.Text);


    // .

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        // // Idea of detaching.
        // // https://www.jeff.wilcox.name/2010/04/template-part-tips/

        if (textBox != null)
        {
            textBox.GotKeyboardFocus -= OnTextBox_GotFocus;
            textBox.LostKeyboardFocus -= OnTextBox_LostFocus;
            textBox.KeyUp -= OnTextBox_KeyUp;
        }

        rootPanel = GetTemplateChild(PartRootPanelName) as Panel;
        textBox = GetTemplateChild(PartTextBoxName) as TextBox;
        searchIcon = GetTemplateChild(PartSearchIconName) as Button;
        closeButton = GetTemplateChild(PartCloseButtonName) as Label;

        if (textBox != null)
        {
            textBox.GotKeyboardFocus += OnTextBox_GotFocus;
            textBox.LostKeyboardFocus += OnTextBox_LostFocus;
            textBox.KeyUp += OnTextBox_KeyUp;
            }
        }
    }
}

我的XAML:

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SearchTextBox" xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase">
<Style TargetType="{x:Type local:SearchTextBox}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:SearchTextBox}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">

                    <Grid Name="PART_RootPanel">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="28"/>
                        </Grid.ColumnDefinitions>

                        <Grid Grid.Column="0">
                            <TextBox Name="PART_TextBox" Background="#FF333337" BorderThickness="0" VerticalContentAlignment="Center" Foreground="White" FontSize="14px" Text=""/>
                            <TextBlock IsHitTestVisible="False" Text="{TemplateBinding HintText}" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="4,0,0,0" FontSize="14px" Foreground="#FF7C7777">
                                <TextBlock.Style>
                                    <Style  TargetType="{x:Type TextBlock}">
                                        <Setter Property="Visibility" Value="Collapsed"/>
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding Text, ElementName=PART_TextBox}" Value="">
                                                <Setter Property="Visibility" Value="Visible"/>
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </TextBlock.Style>
                            </TextBlock>
                        </Grid>
                        <Button Width="28" Grid.Column="1" Name="PART_SearchIcon"  Content="" Background="#FF252526"
                                Focusable="False" Command="{x:Static local:SearchTextBox.ActivateSearchCommand}">
                            <Button.Template>
                                <ControlTemplate TargetType="Button">
                                    <Grid Background="#FF333337">
                                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                                    </Grid>
                                </ControlTemplate>
                            </Button.Template>
                            <Button.Style>
                                <Style TargetType="Button">
                                    <Setter Property="Visibility" Value="Collapsed"/>
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding Text, ElementName=PART_TextBox}" Value="">
                                            <Setter Property="Visibility" Value="Visible"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Button.Style>
                        </Button>
                        <Label Grid.Column="1" Width="28" HorizontalAlignment="Center" HorizontalContentAlignment="Center" Cursor="Hand" VerticalContentAlignment="Center" VerticalAlignment="Stretch" Margin="0" Padding="4" Foreground="White" FontWeight="Bold" Content="x" Name="PART_CloseButton" Focusable="False"
                                   Background="#FF333337">
                            <Label.InputBindings>
                                <MouseBinding Command="{x:Static local:SearchTextBox.CancelSearchCommand}" MouseAction="LeftClick" />
                            </Label.InputBindings>
                         
                            <Label.Style>
                                <Style TargetType="Label">
                                    <Setter Property="Visibility" Value="Visible"/>
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding Text, ElementName=PART_TextBox}" Value="">
                                            <Setter Property="Visibility" Value="Collapsed"/>
                                        </DataTrigger>
                                        <Trigger Property="IsMouseOver" Value="True">
                                            <Setter Property="Background" Value="#000"/>
                                        </Trigger>
                                    </Style.Triggers>
                                </Style>
                            </Label.Style>
                        </Label>
                       
                    </Grid>    
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

控件构建良好,但我无法获取 searchTextBox.Text,它 returns 为 null,并且在 TextChanged 上不触发。有什么想法吗?

我建议您在复制和粘贴之前多做一些研究并了解您正在使用的代码。在使用之前尝试了解它的作用和原因。

  1. 您没有在任何地方引用您的方法 PART_TextBox_TextChanged,它没有在 OnApplyTemplate 中使用,我希望它被分配为处理程序。没有它,它永远不会被调用。我希望在第一个 if 语句中有一个 textBox.TextChanged -= PART_TextBox_TextBox_TextChanged,在第二个语句中有一个 textBox.TextChanged += PART_TextBox_TextBox_TextChanged
  2. 您从未在任何地方设置 TextBoxText 依赖项 属性 的值;既不在代码中也不使用 BindingTemplateBinding。您的 Text 属性 正在引用 TextBoxText 作为它的值,如果它从未设置过,它总是 null 。 public static readonly DependencyProperty TextBoxTextProperty;(连同 static SearchTextBox() 中的代码)声明依赖项 属性 存在,但它的值从未设置。
    在这样的自定义控件中,您可以使用 TemplateBindingPART_TextBoxText 属性 绑定到您的 TextBoxText 属性:
    <TextBox Name="PART_TextBox" ... Text="{TemplateBinding TextBoxText}"/>