WPF实现TextBox输入文字后自动弹出数据(类似百度的输入框)

本文介绍如何在WPF应用中创建一个自定义的AutoCompleteTextbox控件,该控件允许用户通过自动补全功能快速输入文本。通过实现内部逻辑,包括设置文本更改、延迟处理和自动完成列表的更新,该控件能够提高用户输入效率。此外,展示了如何将此控件整合到现有WPF应用中,并提供了示例代码和用法说明。

1. 创建一个继承至Canvas的控件,并命名为WPFAutoCompleteTextbox

2. 在WPFAutoCompleteTextbox.cs中增加以下代码

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
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 WPFAutoCompleteTextbox
{
    /// <summary>
    /// Interaction logic for AutoCompleteTextBox.xaml
    /// </summary>    
    public partial class AutoCompleteTextBox : Canvas
    {
        #region Members
        private VisualCollection controls;
        private TextBox textBox;
        private ComboBox comboBox;
        private ObservableCollection<AutoCompleteEntry> autoCompletionList;
        private System.Timers.Timer keypressTimer;
        private delegate void TextChangedCallback();
        private bool insertText;
        private int delayTime;
        private int searchThreshold;
        #endregion

        #region Constructor
        public AutoCompleteTextBox()
        {
            controls = new VisualCollection(this);
            InitializeComponent();

            autoCompletionList = new ObservableCollection<AutoCompleteEntry>();
            searchThreshold = 2;        // default threshold to 2 char

            // set up the key press timer
            keypressTimer = new System.Timers.Timer();
            keypressTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnTimedEvent);

            // set up the text box and the combo box
            comboBox = new ComboBox();
            comboBox.IsSynchronizedWithCurrentItem = true;
            comboBox.IsTabStop = false;
            comboBox.SelectionChanged += new SelectionChangedEventHandler(comboBox_SelectionChanged);

            textBox = new TextBox();
            textBox.TextChanged += new TextChangedEventHandler(textBox_TextChanged);
            textBox.VerticalContentAlignment = VerticalAlignment.Center;

            controls.Add(comboBox);
            controls.Add(textBox);
        }
        #endregion

        #region Methods
        public string Text
        {
            get { return textBox.Text; }
            set 
            {
                insertText = true;
                textBox.Text = value; 
            }
        }

        public int DelayTime
        {
            get { return delayTime; }
            set { delayTime = value; }
        }

        public int Threshold
        {
            get { return searchThreshold; }
            set { searchThreshold = value; }
        }

        public void AddItem(AutoCompleteEntry entry)
        {
            autoCompletionList.Add(entry);
        }

        private void comboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (null != comboBox.SelectedItem)
            {
                insertText = true;
                ComboBoxItem cbItem = (ComboBoxItem)comboBox.SelectedItem;
                textBox.Text = cbItem.Content.ToString();
            }
        }

        private void TextChanged()
        {
            try
            {
                comboBox.Items.Clear();
                if (textBox.Text.Length >= searchThreshold)
                {
                    foreach (AutoCompleteEntry entry in autoCompletionList)
                    {
                        foreach (string word in entry.KeywordStrings)
                        {
                            if (word.StartsWith(textBox.Text, StringComparison.CurrentCultureIgnoreCase))
                            {
                                ComboBoxItem cbItem = new ComboBoxItem();
                                cbItem.Content = entry.ToString();
                                comboBox.Items.Add(cbItem);
                                break;
                            }
                        }
                    }
                    comboBox.IsDropDownOpen = comboBox.HasItems;
                }
                else
                {
                    comboBox.IsDropDownOpen = false;
                }
            }
            catch { }
        }

        private void OnTimedEvent(object source, System.Timers.ElapsedEventArgs e)
        {
            keypressTimer.Stop();
            Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
                new TextChangedCallback(this.TextChanged));
        }

        private void textBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            // text was not typed, do nothing and consume the flag
            if (insertText == true) insertText = false;
            
            // if the delay time is set, delay handling of text changed
            else
            {
                if (delayTime > 0)
                {
                    keypressTimer.Interval = delayTime;
                    keypressTimer.Start();
                }
                else TextChanged();
            }
        }

        protected override Size ArrangeOverride(Size arrangeSize)
        {
            textBox.Arrange(new Rect(arrangeSize));
            comboBox.Arrange(new Rect(arrangeSize));
            return base.ArrangeOverride(arrangeSize);
        }

        protected override Visual GetVisualChild(int index)
        {
            return controls[index];
        }

        protected override int VisualChildrenCount
        {
            get { return controls.Count; }
        }
        #endregion
    }
}

3. 在WPFAutoCompleteTextbox.xaml中增加以下代码
<Canvas x:Class="WPFAutoCompleteTextbox.AutoCompleteTextBox"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Canvas>

4. 使用创建的WPFAutoCompleteTextbox,新建一个WPF工程,在Windows1.xaml中按以下代码编写
<Window x:Class="WPFAutoCompleteTextbox.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WPFAutoCompleteTextbox"        
    Title="WPF AutoCompleteTextBox" Height="200" Width="300">
    <StackPanel Background="SteelBlue">
        <Button Name="button1" Height="23" Width="75" Margin="20" Click="button1_Click" HorizontalAlignment="Left">Clear</Button>
        <local:AutoCompleteTextBox Height="23" Width="240" x:Name="textBox1" DelayTime="500" Threshold="2"/>
    </StackPanel>
</Window>

5. 在Windows1.cs中按以下代码编写
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
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 WPFAutoCompleteTextbox
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            textBox1.AddItem(new AutoCompleteEntry("Toyota Camry", "Toyota Camry", "camry", "car", "sedan"));
            textBox1.AddItem(new AutoCompleteEntry("Toyota Corolla", "Toyota Corolla", "corolla", "car", "compact"));
            textBox1.AddItem(new AutoCompleteEntry("Toyota Tundra", "Toyota Tundra", "tundra", "truck"));
            textBox1.AddItem(new AutoCompleteEntry("Chevy Impala", null));  // null matching string will default with just the name
            textBox1.AddItem(new AutoCompleteEntry("Chevy Tahoe", "Chevy Tahoe", "tahoe", "truck", "SUV"));
            textBox1.AddItem(new AutoCompleteEntry("Chevrolet Malibu", "Chevrolet Malibu", "malibu", "car", "sedan"));
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            textBox1.Text = string.Empty;
        }
    }
}

6. 编译工程,哈哈,看到效果了吧



<think> 我们正在讨论WPF输入框输入内容后闪退的问题。根据引用[2],如果输入框(如TextBox)参与了拖拽操作,可能会抛出InvalidOperationException异常,而且这个异常无法通过一般的try-catch捕获,会导致程序直接闪退。因此,我们需要考虑阻断此类控件的冒泡路由事件传递。另外,引用[5]提到了当鼠标快速移动进出TextBox时导致的控件显示卡顿,其解决方案是使用异步更新,这也可能是闪退的一个原因,因为UI线程被阻塞。此外,引用[4]中Flutter的例子说明键盘弹出会改变布局,导致刷新,这也可能引发问题。 结合这些引用,我们可以分析WPF输入框输入内容后闪退的可能原因和解决方案: 可能原因: 1. **路由事件问题**:如引用[2]所述,TextBox参与拖拽操作时可能抛出无法捕获的异常,导致闪退。 2. **UI线程阻塞**:输入内容后可能触发复杂的操作(如数据验证、自动完成等),阻塞UI线程,导致响应迟缓甚至崩溃(引用[5])。 3. **布局刷新问题**:输入内容后弹出键盘,键盘弹出改变布局尺寸,导致父组件刷新,可能引发异常(类似引用[4]的Flutter问题,虽然平台不同,但原理相似)。 4. **绑定或自定义控件问题**:如果使用了自定义的文本框控件(如引用[3]的水印文本框),可能存在缺陷,导致输入内容后触发异常。 解决方案如下: ### 1. 处理路由事件异常(针对拖拽问题) - 在包含TextBox的父容器中(如Grid、StackPanel等),监听拖拽相关的事件(如`PreviewDragOver`、`PreviewDrop`等),并将事件标记为已处理,防止事件传递到TextBox。 - 例如,在父容器中添加事件处理: ```xaml <Grid PreviewDragOver="Grid_PreviewDragOver" PreviewDrop="Grid_PreviewDrop"> <TextBox ... /> </Grid> ``` 在事件处理程序中: ```csharp private void Grid_PreviewDragOver(object sender, DragEventArgs e) { e.Handled = true; // 阻止事件继续传递 } private void Grid_PreviewDrop(object sender, DragEventArgs e) { e.Handled = true; } ``` 这样可以避免TextBox参与拖拽,从而防止异常发生[^2]。 ### 2. 避免UI线程阻塞(针对输入处理耗时操作) - 使用异步方式处理输入内容的变化(如`TextChanged`事件)。对于耗时操作(如数据库查询、复杂计算),使用异步方法,并在完成时通过`Dispatcher`更新UI。 - 例如,在TextChanged事件处理中: ```csharp private async void TextBox_TextChanged(object sender, TextChangedEventArgs e) { // 获取当前文本框内容 string text = (sender as TextBox).Text; // 使用异步操作处理文本 await Task.Run(() => { // 模拟耗时操作(如数据验证、搜索建议等) System.Threading.Thread.Sleep(100); }); // 如果需要更新UI,确保通过Dispatcher(如果当前不在UI线程,但上述代码中await返回后仍在UI线程,所以通常不需要) // 例如:Dispatcher.Invoke(() => { ... }); } ``` 注意:避免在异步操作中直接访问UI元素,除非通过Dispatcher[^5]。 ### 3. 处理键盘弹出导致的布局刷新问题 - WPF中键盘弹出会改变布局,可能导致父容器重新计算尺寸。如果父容器中绑定了可能引发异常的数据或复杂的布局逻辑,则可能崩溃。解决方案: - 检查布局相关的绑定和计算,确保在布局变化时不会出现无效数据或计算异常。 - 使用`ScrollViewer`包裹内容,避免整体布局调整。 - 监听键盘事件(如`GotKeyboardFocus`和`LostKeyboardFocus`),在键盘弹出时暂停某些后台操作。 ### 4. 检查自定义控件 - 如果使用了自定义文本框(如引用[3]中的水印文本框),请检查其实现。尤其是当输入内容后,可能触发的属性更改(如`PasswordStr`绑定)是否导致异常。可以尝试使用原生TextBox测试是否仍然闪退,以确定是否自定义控件的问题。 - 如果是自定义控件的问题,需要检查控件代码,特别是依赖属性和事件处理逻辑。 ### 5. 全局异常处理 - 添加全局异常处理,捕捉未处理的异常,至少记录错误信息以便分析: ```csharp // 在App.xaml.cs中 public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); DispatcherUnhandledException += App_DispatcherUnhandledException; } private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) { // 记录异常,例如写入日志文件 // 引用[1]中的日志记录方法 Logger.Error(e.Exception, "Unhandled exception"); e.Handled = true; // 防止应用关闭(但需注意:某些异常可能无法恢复) } } ``` 注意:即使添加了全局异常处理,也应解决根本问题,因为某些异常(如引用[2]提到的拖拽异常)可能无法被捕获[^1][^2]。 ### 总结步骤 1. **阻断拖拽事件传递**:在父容器处理拖拽事件,设置`e.Handled=true`。 2. **异步处理输入**:确保`TextChanged`等事件中的耗时操作异步执行,不阻塞UI线程。 3. **检查布局变化**:优化布局,避免键盘弹出时出现异常。 4. **排查自定义控件**:替换为原生控件测试。 5. **添加全局异常处理**:记录异常日志,便于进一步分析。 按照上述步骤逐一排查,通常可以解决输入框闪退问题。 §§相关问题§§ 1. 如何捕获WPF中无法被try-catch的异常? 2. WPF如何优化大量数据绑定导致的性能问题? 3. 自定义文本框控件时需要注意哪些常见陷阱? </think> 针对WPF输入框内容输入后闪退问题,以下是结合引用内容的系统性解决方案,按优先级排序: ### 一、路由事件冲突问题(最高概率) 引用[2]明确指出控件拖拽会导致**无法捕获的异常**: ```csharp // 在父容器中阻断冒泡事件(如Grid/StackPanel) private void ParentContainer_PreviewMouseMove(object sender, MouseEventArgs e) { if (e.OriginalSource is TextBox || e.OriginalSource is ComboBox) { e.Handled = true; // 阻断事件传递 } } ``` **操作步骤**: 1. 在XAML中为输入框的父容器添加事件: ```xaml <Grid PreviewMouseMove="ParentContainer_PreviewMouseMove"> <TextBox x:Name="inputBox"/> </Grid> ``` 2. 确保所有拖拽相关事件(`PreviewDragEnter`/`PreviewDragOver`)同样处理[^2] ### 二、异步更新优化(输入卡顿触发崩溃) 引用[5]强调UI线程阻塞会导致卡顿崩溃: ```csharp private async void inputBox_TextChanged(object sender, TextChangedEventArgs e) { // 将耗时操作移出UI线程 await Task.Run(() => { // 数据验证/网络请求等耗时操作 var result = HeavyOperation(inputBox.Text); // 返回UI线程更新 Dispatcher.Invoke(() => outputBlock.Text = result); }); } ``` **关键点**: - 使用`async/await`分离耗时操作 - 通过`Dispatcher`安全更新UI控件[^5] ### 三、自定义控件缺陷排查 若使用类似引用[3]的自定义水印文本框: ```xaml <local:SJTextBox WaterRemark="请输入" PasswordStr="{Binding Pwd}"/> ``` **排查步骤**: 1. 临时替换为原生`TextBox`测试是否仍崩溃 2. 检查绑定属性`PasswordStr`的`setter`逻辑: ```csharp public string PasswordStr { get => _pwd; set { if(_pwd == value) return; // 避免循环更新 _pwd = value; OnPropertyChanged(); } } ``` 3. 移除`WaterRemark`属性测试是否为水印渲染问题[^3] ### 四、键盘布局刷新冲突 引用[4]揭示键盘弹出导致的尺寸重计算问题: ```xaml <!-- 在Window/UserControl级别添加 --> <SizeToContent="Height" VerticalAlignment="Top" FlowDirection="LeftToRight"/> ``` **优化方案**: 1. 禁用窗口自动缩放:`SizeToContent="Manual"` 2. 使用`ScrollViewer`包裹输入区域避免布局重排: ```xaml <ScrollViewer VerticalScrollBarVisibility="Auto"> <StackPanel> <TextBox/> <!-- 输入框放在固定容器内 --> </StackPanel> </ScrollViewer> ``` ### 五、全局异常捕获(兜底方案) 引用[1]的日志系统记录崩溃现场: ```csharp // App.xaml.cs中注册全局异常处理 public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { DispatcherUnhandledException += (s, ex) => { Logger.Fatal(ex.Exception, "输入崩溃"); // 使用NLog等记录器 ex.Handled = true; // 阻止退出 }; } } ``` **日志配置要点**: ```xml <nlog> <targets> <target name="file" type="File" fileName="${shortdate}.log" /> </targets> <rules> <logger name="*" minlevel="Debug" writeTo="file" /> </rules> </nlog> ``` ### 典型解决流程 ```mermaid graph TD A[发生闪退] --> B{是否自定义控件?} B -->|是| C[检查绑定/水印逻辑] B -->|否| D[添加路由事件阻断] C --> E[测试原生TextBox] D --> F[实现异步更新] E --> F F --> G[优化布局防重排] G --> H[启用全局异常日志] ```
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值