WPF之TextBox控件詳解

文章目錄

    • 1. TextBox概述
    • 2. 基本屬性與功能
    • 3. 輸入控制詳解
      • 3.1 MaxLength
      • 3.2 AcceptsReturn
      • 3.3 AcceptsTab
      • 3.4 CharacterCasing
      • 3.5 IsUndoEnabled
      • 3.6 自定義輸入限制
    • 4. 文本選擇與操作
      • 4.1 選擇屬性
      • 4.2 選擇方法
      • 4.3 文本操作
      • 4.4 選擇事件
      • 4.5 實現自定義文本處理功能
    • 5. 滾動支持
      • 5.1 滾動條可見性
      • 5.2 禁用水平滾動
      • 5.3 編程控制滾動位置
      • 5.4 滾動事件
      • 5.5 自動滾動示例
    • 6. 數據驗證
      • 6.1 基本驗證方法
      • 6.2 使用IDataErrorInfo接口
      • 6.3 使用ValidationRule
      • 6.4 錯誤樣式和錯誤模板
      • 6.5 實時驗證示例
    • 7. 撤銷/重做支持
      • 7.1 啟用/禁用撤銷功能
      • 7.2 撤銷和重做方法
      • 7.3 自定義撤銷重做按鈕
      • 7.4 撤銷單元
      • 7.5 清除撤銷/重做歷史
      • 7.6 高級撤銷/重做示例
    • 8. 文本綁定與更新
      • 8.1 基本綁定
      • 8.2 綁定模式
      • 8.3 更新觸發器
      • 8.4 字符串格式化
      • 8.5 值轉換器
      • 8.6 FallbackValue 和 TargetNullValue
      • 8.7 綁定到集合項
      • 8.8 綁定命令
      • 8.9 實時搜索示例
      • 8.10 表單數據綁定示例
    • 9. 特殊文本框:PasswordBox與RichTextBox
      • 9.1 PasswordBox
        • 9.1.1 基本屬性
        • 9.1.2 事件處理
        • 9.1.3 PasswordBox與數據綁定問題
        • 9.1.4 安全最佳實踐
      • 9.2 RichTextBox
        • 9.2.1 基本用法
        • 9.2.2 內容操作
        • 9.2.3 格式化文本
        • 9.2.4 插入圖片和其他對象
        • 9.2.5 文件保存與加載
        • 9.2.6 創建簡單的文本編輯器
    • 10. 樣式與模板定制
      • 10.1 基本樣式設置
      • 10.2 使用Style對象
      • 10.3 使用觸發器
      • 10.4 樣式繼承
      • 10.5 隱式樣式
      • 10.6 控件模板定制
      • 10.7 自定義附加屬性
      • 10.8 自定義控件
      • 10.9 使用第三方控件庫
      • 10.10 動態樣式效果
    • 11. 常見應用場景
      • 11.1 登錄表單
      • 11.2 搜索框
      • 11.3 富文本編輯器
      • 11.4 數值輸入控件
      • 11.5 自動完成文本框
      • 11.6 配置文本編輯器
    • 12. 總結與最佳實踐

可以根據Github拉取示例程序運行
GitHub程序演示地址(點擊直達)
也可以在本文資源中下載
在這里插入圖片描述

1. TextBox概述

TextBox控件是WPF中用于文本輸入和編輯的基本控件,它繼承自TextBoxBase類,提供了文本編輯的核心功能。它可以用于單行文本輸入(如表單字段)、多行文本編輯(如注釋或描述)等場景。

TextBox的類層次結構如下:

DependencyObject
Visual
UIElement
FrameworkElement
Control
TextBoxBase
TextBox

2. 基本屬性與功能

TextBox控件的主要屬性包括:

  • Text:獲取或設置文本框中的文本內容
  • FontFamilyFontSizeFontWeight等:控制文本顯示的字體屬性
  • TextAlignment:控制文本的對齊方式
  • TextWrapping:控制文本的換行行為
  • IsReadOnly:設置文本框是否為只讀狀態

下面是一個基本的TextBox控件示例:

<TextBox x:Name="txtBasic" Width="200" Height="30" Text="基本文本框" FontSize="14"TextAlignment="Center" />

C#代碼中操作TextBox:

// 獲取文本框內容
string content = txtBasic.Text;// 設置文本框內容
txtBasic.Text = "新的文本內容";// 設置字體
txtBasic.FontFamily = new FontFamily("Arial");
txtBasic.FontSize = 16;
txtBasic.FontWeight = FontWeights.Bold;// 設置只讀狀態
txtBasic.IsReadOnly = true;

3. 輸入控制詳解

TextBox提供了多種屬性來控制用戶輸入:

3.1 MaxLength

MaxLength屬性用于限制用戶可以輸入的最大字符數。默認值為0,表示沒有限制。

// 限制最多輸入50個字符
txtInput.MaxLength = 50;

3.2 AcceptsReturn

AcceptsReturn屬性決定當用戶按下回車鍵時,是否在文本中插入換行符。對于多行文本框,通常設置為true。

<TextBox AcceptsReturn="True" TextWrapping="Wrap"Height="100" Width="200" />

3.3 AcceptsTab

AcceptsTab屬性決定當用戶按下Tab鍵時,是否在文本中插入制表符。默認情況下,Tab鍵用于在控件間移動焦點。

<TextBox AcceptsTab="True" AcceptsReturn="True"TextWrapping="Wrap"Height="100" Width="200" />

3.4 CharacterCasing

CharacterCasing屬性控制輸入的字符自動轉換為大寫、小寫或保持原樣。

// 自動將用戶輸入的字符轉換為大寫
txtUpperCase.CharacterCasing = CharacterCasing.Upper;// 自動將用戶輸入的字符轉換為小寫
txtLowerCase.CharacterCasing = CharacterCasing.Lower;// 保持用戶輸入的原始大小寫(默認值)
txtNormal.CharacterCasing = CharacterCasing.Normal;

3.5 IsUndoEnabled

IsUndoEnabled屬性控制是否啟用撤銷/重做功能。默認為true。

// 禁用撤銷/重做功能
txtNoUndo.IsUndoEnabled = false;

3.6 自定義輸入限制

除了使用內置屬性外,我們還可以通過處理TextBox的PreviewTextInputPreviewKeyDown事件來實現自定義的輸入限制,例如只允許輸入數字:

private void NumericTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{// 使用正則表達式判斷輸入是否為數字Regex regex = new Regex("[^0-9]+");e.Handled = regex.IsMatch(e.Text);
}private void NumericTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{// 允許空格鍵和特殊控制鍵if (e.Key == Key.Space){e.Handled = true;}
}

以下是一個完整的示例,實現了一個只接受數字輸入的TextBox:

<TextBox x:Name="txtNumeric"PreviewTextInput="NumericTextBox_PreviewTextInput"PreviewKeyDown="NumericTextBox_PreviewKeyDown"Width="200" />
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Input;namespace WpfSample
{public partial class MainWindow : Window{public MainWindow(){InitializeComponent();}private void NumericTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e){// 使用正則表達式判斷輸入是否為數字Regex regex = new Regex("[^0-9]+");e.Handled = regex.IsMatch(e.Text);}private void NumericTextBox_PreviewKeyDown(object sender, KeyEventArgs e){// 禁止輸入空格if (e.Key == Key.Space){e.Handled = true;}}}
}

4. 文本選擇與操作

TextBox提供了豐富的文本選擇和操作功能,可以通過屬性和方法來實現:

4.1 選擇屬性

  • SelectionStart:獲取或設置選定內容的起始位置
  • SelectionLength:獲取或設置選定內容的長度
  • SelectedText:獲取或設置選定的文本
  • CaretIndex:獲取或設置插入符號(光標)位置
// 選擇從位置5開始的10個字符
txtEditor.SelectionStart = 5;
txtEditor.SelectionLength = 10;// 獲取選中的文本
string selectedText = txtEditor.SelectedText;// 替換選中的文本
txtEditor.SelectedText = "新文本";// 設置光標位置
txtEditor.CaretIndex = 15;

4.2 選擇方法

TextBox提供了幾個用于文本選擇的便捷方法:

  • Select(int start, int length):選擇指定范圍的文本
  • SelectAll():選擇所有文本
  • Clear():清除所有文本
// 選擇從位置0開始的5個字符
txtEditor.Select(0, 5);// 選擇所有文本
txtEditor.SelectAll();// 清除所有文本
txtEditor.Clear();

4.3 文本操作

以下是一些常見的文本操作示例:

// 在當前光標位置插入文本
int caretIndex = txtEditor.CaretIndex;
txtEditor.Text = txtEditor.Text.Insert(caretIndex, "插入的文本");
txtEditor.CaretIndex = caretIndex + "插入的文本".Length;// 刪除選中的文本
txtEditor.SelectedText = string.Empty;// 復制選中的文本到剪貼板
if (!string.IsNullOrEmpty(txtEditor.SelectedText))
{Clipboard.SetText(txtEditor.SelectedText);
}// 將剪貼板內容粘貼到光標位置
if (Clipboard.ContainsText())
{txtEditor.SelectedText = Clipboard.GetText();
}

4.4 選擇事件

TextBox提供了與選擇相關的事件:

  • SelectionChanged:當選擇的內容改變時觸發
private void TextBox_SelectionChanged(object sender, RoutedEventArgs e)
{TextBox textBox = sender as TextBox;// 顯示當前選擇的信息statusInfo.Text = $"選擇起始位置: {textBox.SelectionStart}, 選擇長度: {textBox.SelectionLength}";
}

4.5 實現自定義文本處理功能

下面是一個示例,實現了一個簡單的文本統計功能:

<StackPanel><TextBox x:Name="txtContent" AcceptsReturn="True" TextWrapping="Wrap"Height="200" Width="400"SelectionChanged="TextBox_SelectionChanged"/><TextBlock x:Name="txtStats" Margin="0,10,0,0"Text="文本統計信息將顯示在這里"/>
</StackPanel>
private void TextBox_SelectionChanged(object sender, RoutedEventArgs e)
{TextBox textBox = sender as TextBox;// 計算文本統計信息int charCount = textBox.Text.Length;int lineCount = textBox.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.None).Length;int wordCount = textBox.Text.Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Length;// 計算選中部分的統計信息int selectedCharCount = textBox.SelectedText.Length;int selectedLineCount = textBox.SelectedText.Split(new[] { Environment.NewLine }, StringSplitOptions.None).Length;int selectedWordCount = textBox.SelectedText.Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Length;// 更新統計信息顯示txtStats.Text = $"字符數: {charCount}, 行數: {lineCount}, 單詞數: {wordCount}\n" +$"選中內容 - 字符數: {selectedCharCount}, 行數: {selectedLineCount}, 單詞數: {selectedWordCount}";
}

5. 滾動支持

對于多行文本,TextBox內置了滾動支持功能,可以通過以下屬性控制滾動行為:

5.1 滾動條可見性

  • HorizontalScrollBarVisibility:控制水平滾動條的顯示
  • VerticalScrollBarVisibility:控制垂直滾動條的顯示

這兩個屬性接受以下值:

  • ScrollBarVisibility.Auto:根據內容自動顯示或隱藏滾動條
  • ScrollBarVisibility.Visible:始終顯示滾動條
  • ScrollBarVisibility.Hidden:隱藏滾動條,但內容仍可滾動
  • ScrollBarVisibility.Disabled:禁用滾動功能
<TextBox AcceptsReturn="True"TextWrapping="Wrap"Height="200"Width="300"VerticalScrollBarVisibility="Auto"HorizontalScrollBarVisibility="Auto"/>

5.2 禁用水平滾動

通常,對于多行文本框,我們希望文本能夠自動換行而不是水平滾動。可以通過設置TextWrappingHorizontalScrollBarVisibility來實現:

<TextBox AcceptsReturn="True"TextWrapping="Wrap"Height="200"Width="300"VerticalScrollBarVisibility="Auto"HorizontalScrollBarVisibility="Disabled"/>

5.3 編程控制滾動位置

TextBox繼承自TextBoxBase,它提供了一些方法來控制滾動位置:

  • ScrollToHome():滾動到文檔開始
  • ScrollToEnd():滾動到文檔結尾
  • ScrollToLine(int lineIndex):滾動到指定行
  • ScrollToHorizontalOffset(double offset):滾動到指定的水平偏移
  • ScrollToVerticalOffset(double offset):滾動到指定的垂直偏移
// 滾動到文檔結尾
txtLog.ScrollToEnd();// 滾動到文檔開始
txtLog.ScrollToHome();// 滾動到第10行
txtLog.ScrollToLine(9); // 行索引從0開始// 滾動到指定的垂直偏移
txtLog.ScrollToVerticalOffset(100);

5.4 滾動事件

可以通過處理滾動相關的事件來響應用戶的滾動操作:

private void TextBox_ScrollChanged(object sender, ScrollChangedEventArgs e)
{// 獲取當前滾動位置double verticalOffset = e.VerticalOffset;double horizontalOffset = e.HorizontalOffset;// 獲取內容的總高度和寬度double extentHeight = e.ExtentHeight;double extentWidth = e.ExtentWidth;// 判斷是否已滾動到底部bool atBottom = (verticalOffset + e.ViewportHeight) >= extentHeight;// 更新狀態信息statusInfo.Text = $"垂直滾動位置: {verticalOffset}, 是否到底部: {atBottom}";
}

5.5 自動滾動示例

以下是一個模擬日志查看器的示例,實現新內容添加時自動滾動到底部:

<StackPanel><TextBox x:Name="txtLog"IsReadOnly="True"AcceptsReturn="True"TextWrapping="Wrap"Height="300"Width="400"VerticalScrollBarVisibility="Auto"ScrollChanged="TextBox_ScrollChanged"/><Button Content="添加日志條目" Click="AddLogEntry_Click" Margin="0,10,0,0"/>
</StackPanel>
private bool autoScroll = true;private void TextBox_ScrollChanged(object sender, ScrollChangedEventArgs e)
{// 檢測用戶是否手動滾動if (e.ExtentHeightChange == 0){// 用戶手動滾動// 判斷是否滾動到底部autoScroll = (e.VerticalOffset + e.ViewportHeight) >= e.ExtentHeight;}else if (autoScroll){// 如果設置了自動滾動,內容變化時滾動到底部((TextBox)sender).ScrollToEnd();}
}private void AddLogEntry_Click(object sender, RoutedEventArgs e)
{// 添加一條日志條目string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");string logEntry = $"[{timestamp}] 日志條目 #{++logCounter}\n";txtLog.AppendText(logEntry);// 如果啟用了自動滾動,滾動到底部if (autoScroll){txtLog.ScrollToEnd();}
}private int logCounter = 0;

6. 數據驗證

TextBox控件支持多種數據驗證方式,可以確保用戶輸入的內容符合應用程序的要求。

6.1 基本驗證方法

最簡單的驗證方法是在TextBox的Text屬性變化時進行驗證:

private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{TextBox textBox = sender as TextBox;string text = textBox.Text;// 進行驗證bool isValid = !string.IsNullOrEmpty(text) && text.Length >= 3;// 根據驗證結果更新界面if (!isValid){textBox.Background = new SolidColorBrush(Colors.LightPink);validationMessage.Text = "內容長度不能少于3個字符";}else{textBox.Background = new SolidColorBrush(Colors.White);validationMessage.Text = "";}
}

6.2 使用IDataErrorInfo接口

更結構化的方法是在數據模型中實現IDataErrorInfo接口,并將TextBox綁定到該數據模型:

public class UserViewModel : IDataErrorInfo
{private string _username;public string Username{get { return _username; }set { _username = value; }}// IDataErrorInfo接口實現public string Error{get { return null; }}public string this[string columnName]{get{string result = null;if (columnName == "Username"){if (string.IsNullOrEmpty(Username)){result = "用戶名不能為空";}else if (Username.Length < 3){result = "用戶名長度不能少于3個字符";}}return result;}}
}

在XAML中綁定:

<TextBox x:Name="txtUsername"Text="{Binding Username, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"Width="200"/>

6.3 使用ValidationRule

WPF還提供了ValidationRule類,可以為綁定定義驗證規則:

public class UsernameValidationRule : ValidationRule
{public override ValidationResult Validate(object value, CultureInfo cultureInfo){string username = value as string;if (string.IsNullOrEmpty(username)){return new ValidationResult(false, "用戶名不能為空");}if (username.Length < 3){return new ValidationResult(false, "用戶名長度不能少于3個字符");}return ValidationResult.ValidResult;}
}

在XAML中使用:

<TextBox Width="200"><TextBox.Text><Binding Path="Username" UpdateSourceTrigger="PropertyChanged"><Binding.ValidationRules><local:UsernameValidationRule ValidatesOnTargetUpdated="True"/></Binding.ValidationRules></Binding></TextBox.Text>
</TextBox>

6.4 錯誤樣式和錯誤模板

可以自定義驗證錯誤的顯示方式,通過設置Validation.ErrorTemplate

<TextBox Width="200"><TextBox.Text><Binding Path="Username" UpdateSourceTrigger="PropertyChanged"><Binding.ValidationRules><local:UsernameValidationRule ValidatesOnTargetUpdated="True"/></Binding.ValidationRules></Binding></TextBox.Text><TextBox.Style><Style TargetType="TextBox"><Style.Triggers><Trigger Property="Validation.HasError" Value="True"><Setter Property="ToolTip"Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}"/><Setter Property="Background" Value="Pink"/></Trigger></Style.Triggers></Style></TextBox.Style>
</TextBox>

自定義錯誤模板:

<TextBox Width="200"><TextBox.Text><Binding Path="Username" UpdateSourceTrigger="PropertyChanged"><Binding.ValidationRules><local:UsernameValidationRule ValidatesOnTargetUpdated="True"/></Binding.ValidationRules></Binding></TextBox.Text><Validation.ErrorTemplate><ControlTemplate><StackPanel><AdornedElementPlaceholder/><TextBlock Text="{Binding ErrorContent}" Foreground="Red"/></StackPanel></ControlTemplate></Validation.ErrorTemplate>
</TextBox>

6.5 實時驗證示例

下面是一個綜合示例,實現了一個帶實時驗證的表單:

<StackPanel Margin="10"><TextBlock Text="用戶名:" Margin="0,0,0,5"/><TextBox x:Name="txtUsername" Width="250" HorizontalAlignment="Left"Text="{Binding Username, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/><TextBlock Text="郵箱:" Margin="0,10,0,5"/><TextBox x:Name="txtEmail" Width="250" HorizontalAlignment="Left"Text="{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/><TextBlock Text="密碼:" Margin="0,10,0,5"/><PasswordBox x:Name="txtPassword" Width="250" HorizontalAlignment="Left"PasswordChanged="PasswordBox_PasswordChanged"/><Button Content="提交" Margin="0,20,0,0" Width="100" HorizontalAlignment="Left"IsEnabled="{Binding IsFormValid}"/>
</StackPanel>
public class FormViewModel : INotifyPropertyChanged, IDataErrorInfo
{private string _username;public string Username{get { return _username; }set { _username = value;OnPropertyChanged(nameof(Username));ValidateForm();}}private string _email;public string Email{get { return _email; }set { _email = value; OnPropertyChanged(nameof(Email));ValidateForm();}}private string _password;public string Password{get { return _password; }set { _password = value; OnPropertyChanged(nameof(Password));ValidateForm();}}private bool _isFormValid;public bool IsFormValid{get { return _isFormValid; }set { _isFormValid = value; OnPropertyChanged(nameof(IsFormValid));}}private void ValidateForm(){// 檢查所有字段的有效性bool usernameValid = !string.IsNullOrEmpty(Username) && Username.Length >= 3;bool emailValid = !string.IsNullOrEmpty(Email) && Email.Contains("@") && Email.Contains(".");bool passwordValid = !string.IsNullOrEmpty(Password) && Password.Length >= 6;IsFormValid = usernameValid && emailValid && passwordValid;}public string Error => null;public string this[string columnName]{get{switch (columnName){case nameof(Username):if (string.IsNullOrEmpty(Username))return "用戶名不能為空";if (Username.Length < 3)return "用戶名長度不能少于3個字符";break;case nameof(Email):if (string.IsNullOrEmpty(Email))return "郵箱不能為空";if (!Email.Contains("@") || !Email.Contains("."))return "郵箱格式不正確";break;case nameof(Password):if (string.IsNullOrEmpty(Password))return "密碼不能為空";if (Password.Length < 6)return "密碼長度不能少于6個字符";break;}return null;}}public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}
}

7. 撤銷/重做支持

TextBox控件內置了撤銷(Undo)和重做(Redo)功能,讓用戶可以方便地回退和重復編輯操作。

7.1 啟用/禁用撤銷功能

通過IsUndoEnabled屬性可以控制是否啟用撤銷功能:

// 禁用撤銷功能
txtEditor.IsUndoEnabled = false;// 啟用撤銷功能
txtEditor.IsUndoEnabled = true;

7.2 撤銷和重做方法

TextBox提供了以下方法來執行撤銷和重做操作:

  • Undo():撤銷最近的編輯操作
  • Redo():重做最近撤銷的操作
  • CanUndo:獲取一個值,指示是否可以撤銷當前操作
  • CanRedo:獲取一個值,指示是否可以重做先前撤銷的操作
// 檢查是否可以撤銷
if (txtEditor.CanUndo)
{// 撤銷操作txtEditor.Undo();
}// 檢查是否可以重做
if (txtEditor.CanRedo)
{// 重做操作txtEditor.Redo();
}

7.3 自定義撤銷重做按鈕

以下是一個示例,展示如何添加自定義的撤銷和重做按鈕:

<StackPanel><TextBox x:Name="txtEditor"AcceptsReturn="True"TextWrapping="Wrap"Height="200"Width="400"TextChanged="TextBox_TextChanged"/><StackPanel Orientation="Horizontal" Margin="0,10,0,0"><Button x:Name="btnUndo" Content="撤銷" Width="80" Click="Undo_Click" IsEnabled="False" Margin="0,0,10,0"/><Button x:Name="btnRedo" Content="重做" Width="80" Click="Redo_Click" IsEnabled="False"/></StackPanel>
</StackPanel>
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{// 更新撤銷和重做按鈕的狀態UpdateUndoRedoButtons();
}private void Undo_Click(object sender, RoutedEventArgs e)
{if (txtEditor.CanUndo){txtEditor.Undo();UpdateUndoRedoButtons();}
}private void Redo_Click(object sender, RoutedEventArgs e)
{if (txtEditor.CanRedo){txtEditor.Redo();UpdateUndoRedoButtons();}
}private void UpdateUndoRedoButtons()
{btnUndo.IsEnabled = txtEditor.CanUndo;btnRedo.IsEnabled = txtEditor.CanRedo;
}

7.4 撤銷單元

TextBox控件將操作分組為撤銷單元(Undo Units)。一個撤銷單元可能包含多個相關的編輯操作,如一個完整的單詞輸入或刪除。

要手動創建新的撤銷單元,可以使用UndoAction方法:

// 在開始新的編輯操作前調用此方法以創建新的撤銷單元
txtEditor.UndoAction = UndoAction.Create;

這在實現自定義編輯功能時很有用,例如,當執行查找替換操作時,可以將整個操作作為一個撤銷單元。

7.5 清除撤銷/重做歷史

如果需要清除所有撤銷和重做歷史記錄,可以使用:

// 清除撤銷/重做歷史
txtEditor.IsUndoEnabled = false;
txtEditor.IsUndoEnabled = true;

7.6 高級撤銷/重做示例

下面是一個更完整的示例,實現了一個簡單的文本編輯器,包括撤銷/重做功能和狀態顯示:

<Grid><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions><ToolBar Grid.Row="0"><Button x:Name="btnUndo" Content="撤銷" Click="Undo_Click" IsEnabled="False"><Button.ToolTip><ToolTip x:Name="undoTooltip" Content="撤銷"/></Button.ToolTip></Button><Button x:Name="btnRedo" Content="重做" Click="Redo_Click" IsEnabled="False"><Button.ToolTip><ToolTip x:Name="redoTooltip" Content="重做"/></Button.ToolTip></Button><Separator/><Button Content="新建撤銷單元" Click="NewUndoUnit_Click"/></ToolBar><TextBox x:Name="txtEditor"Grid.Row="1"AcceptsReturn="True"TextWrapping="Wrap"VerticalScrollBarVisibility="Auto"TextChanged="TextBox_TextChanged"/><StatusBar Grid.Row="2"><TextBlock x:Name="txtStatus" Text="就緒"/></StatusBar>
</Grid>
private Stack<string> _undoDescriptions = new Stack<string>();
private Stack<string> _redoDescriptions = new Stack<string>();
private string _lastText = string.Empty;private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{// 記錄變更描述if (txtEditor.CanUndo && _lastText != txtEditor.Text){// 簡單的變更描述邏輯string currentText = txtEditor.Text;if (currentText.Length > _lastText.Length){_undoDescriptions.Push("添加文本");}else{_undoDescriptions.Push("刪除文本");}_lastText = currentText;}// 更新撤銷和重做按鈕的狀態和描述UpdateUndoRedoUI();
}private void Undo_Click(object sender, RoutedEventArgs e)
{if (txtEditor.CanUndo){if (_undoDescriptions.Count > 0){string description = _undoDescriptions.Pop();_redoDescriptions.Push(description);}txtEditor.Undo();_lastText = txtEditor.Text;UpdateUndoRedoUI();}
}private void Redo_Click(object sender, RoutedEventArgs e)
{if (txtEditor.CanRedo){if (_redoDescriptions.Count > 0){string description = _redoDescriptions.Pop();_undoDescriptions.Push(description);}txtEditor.Redo();_lastText = txtEditor.Text;UpdateUndoRedoUI();}
}private void NewUndoUnit_Click(object sender, RoutedEventArgs e)
{// 創建新的撤銷單元txtEditor.UndoAction = UndoAction.Create;txtStatus.Text = "已創建新的撤銷單元";
}private void UpdateUndoRedoUI()
{btnUndo.IsEnabled = txtEditor.CanUndo;btnRedo.IsEnabled = txtEditor.CanRedo;// 更新撤銷按鈕的提示if (txtEditor.CanUndo && _undoDescriptions.Count > 0){undoTooltip.Content = $"撤銷: {_undoDescriptions.Peek()}";}else{undoTooltip.Content = "撤銷";}// 更新重做按鈕的提示if (txtEditor.CanRedo && _redoDescriptions.Count > 0){redoTooltip.Content = $"重做: {_redoDescriptions.Peek()}";}else{redoTooltip.Content = "重做";}// 更新狀態信息txtStatus.Text = txtEditor.CanUndo ? "可以撤銷" + (_undoDescriptions.Count > 0 ? $": {_undoDescriptions.Peek()}" : "") :"不可撤銷";
}

8. 文本綁定與更新

WPF的數據綁定是其最強大的特性之一,TextBox控件可以與各種數據源綁定,實現數據的雙向同步。

8.1 基本綁定

最簡單的綁定是將TextBox的Text屬性綁定到數據源:

<TextBox Text="{Binding Name}" Width="200"/>

這里的Name是綁定上下文中的一個屬性。

8.2 綁定模式

綁定有不同的模式,控制數據如何在源和目標之間流動:

  • OneWay:數據從源到目標(TextBox只讀)
  • TwoWay:數據雙向流動(默認)
  • OneTime:數據只在應用程序啟動或數據上下文更改時從源到目標
  • OneWayToSource:數據從目標到源
<!-- 只讀文本框,顯示數據但不允許編輯更新源 -->
<TextBox Text="{Binding Name, Mode=OneWay}" IsReadOnly="True" Width="200"/><!-- 雙向綁定,允許用戶編輯并更新源 -->
<TextBox Text="{Binding Name, Mode=TwoWay}" Width="200"/><!-- 一次性綁定,只在初始化時從源獲取數據 -->
<TextBox Text="{Binding Name, Mode=OneTime}" Width="200"/><!-- 從TextBox到源的單向綁定(少見) -->
<TextBox Text="{Binding Name, Mode=OneWayToSource}" Width="200"/>

8.3 更新觸發器

對于TextBox,可以通過UpdateSourceTrigger屬性控制何時將用戶輸入的值更新到源:

  • PropertyChanged:屬性更改時更新源(實時)
  • LostFocus:失去焦點時更新源(默認)
  • Explicit:僅在調用BindingExpression.UpdateSource()時更新源
<!-- 實時更新,用戶每次輸入都會更新源 -->
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Width="200"/><!-- 默認行為,失去焦點時才更新源 -->
<TextBox Text="{Binding Name, UpdateSourceTrigger=LostFocus}" Width="200"/><!-- 顯式更新,需要代碼中調用UpdateSource() -->
<TextBox x:Name="txtExplicit" Text="{Binding Name, UpdateSourceTrigger=Explicit}" Width="200"/>
<Button Content="更新" Click="UpdateButton_Click"/>
private void UpdateButton_Click(object sender, RoutedEventArgs e)
{// 顯式更新綁定源BindingExpression binding = txtExplicit.GetBindingExpression(TextBox.TextProperty);binding.UpdateSource();
}

8.4 字符串格式化

可以使用StringFormat屬性對顯示的文本進行格式化:

<!-- 顯示帶貨幣符號的金額 -->
<TextBox Text="{Binding Price, StringFormat=C}" Width="200"/><!-- 自定義格式 -->
<TextBox Text="{Binding Date, StringFormat=日期: {0:yyyy-MM-dd}}" Width="200"/>

8.5 值轉換器

如果需要在綁定的源和目標之間進行數據轉換,可以使用值轉換器:

public class BoolToYesNoConverter : IValueConverter
{public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (value is bool boolValue){return boolValue ? "是" : "否";}return "未知";}public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){if (value is string stringValue){return stringValue == "是";}return false;}
}

在XAML中使用轉換器:

<Window.Resources><local:BoolToYesNoConverter x:Key="boolToYesNoConverter"/>
</Window.Resources><TextBox Text="{Binding IsActive, Converter={StaticResource boolToYesNoConverter}}" Width="200"/>

8.6 FallbackValue 和 TargetNullValue

處理空值或綁定失敗的情況:

<!-- 當綁定源為null時顯示"未設置" -->
<TextBox Text="{Binding Name, TargetNullValue='未設置'}" Width="200"/><!-- 當綁定失敗時顯示"綁定錯誤" -->
<TextBox Text="{Binding Name, FallbackValue='綁定錯誤'}" Width="200"/>

8.7 綁定到集合項

可以將TextBox綁定到集合中的項:

<!-- 綁定到當前選定的項目的Name屬性 -->
<TextBox Text="{Binding SelectedItem.Name, ElementName=listView}" Width="200"/>

8.8 綁定命令

TextBox本身不直接支持Command屬性,但可以通過InputBindings添加命令支持:

<TextBox x:Name="txtCommand" Width="200"><TextBox.InputBindings><KeyBinding Key="Enter" Command="{Binding SubmitCommand}" CommandParameter="{Binding Text, ElementName=txtCommand}"/></TextBox.InputBindings>
</TextBox>

8.9 實時搜索示例

以下是一個使用綁定實現實時搜索功能的示例:

<StackPanel><TextBox x:Name="txtSearch" Text="{Binding SearchTerm, UpdateSourceTrigger=PropertyChanged}" Width="200" Margin="0,0,0,10"Padding="5"FontSize="14"VerticalContentAlignment="Center"/><ListBox x:Name="resultsList"ItemsSource="{Binding FilteredItems}"Height="300"Width="200"/>
</StackPanel>
public class SearchViewModel : INotifyPropertyChanged
{private ObservableCollection<string> _allItems;private ObservableCollection<string> _filteredItems;private string _searchTerm;public SearchViewModel(){// 初始化數據_allItems = new ObservableCollection<string>{"蘋果", "香蕉", "橙子", "草莓", "葡萄", "西瓜", "菠蘿", "芒果", "櫻桃", "藍莓"};_filteredItems = new ObservableCollection<string>(_allItems);}public ObservableCollection<string> FilteredItems{get { return _filteredItems; }set { _filteredItems = value; OnPropertyChanged(nameof(FilteredItems));}}public string SearchTerm{get { return _searchTerm; }set { _searchTerm = value; OnPropertyChanged(nameof(SearchTerm));// 根據搜索詞過濾項目FilterItems();}}private void FilterItems(){if (string.IsNullOrEmpty(SearchTerm)){// 如果搜索詞為空,顯示所有項目FilteredItems = new ObservableCollection<string>(_allItems);}else{// 根據搜索詞過濾項目var filtered = _allItems.Where(i => i.Contains(SearchTerm, StringComparison.OrdinalIgnoreCase));FilteredItems = new ObservableCollection<string>(filtered);}}public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}
}

8.10 表單數據綁定示例

下面是一個使用數據綁定的表單示例:

<Grid Margin="20"><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions><!-- 姓名 --><TextBlock Grid.Row="0" Grid.Column="0" Text="姓名:" Margin="0,0,0,5"/><TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"Margin="0,0,0,10" Height="30"/><!-- 電子郵件 --><TextBlock Grid.Row="1" Grid.Column="0" Text="電子郵件:" Margin="0,0,0,5"/><TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"Margin="0,0,0,10" Height="30"/><!-- 年齡 --><TextBlock Grid.Row="2" Grid.Column="0" Text="年齡:" Margin="0,0,0,5"/><TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Age, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"Margin="0,0,0,10" Height="30"/><!-- 備注 --><TextBlock Grid.Row="3" Grid.Column="0" Text="備注:" Margin="0,0,0,5"/><TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Remarks, UpdateSourceTrigger=PropertyChanged}"AcceptsReturn="True"TextWrapping="Wrap"Height="100"Margin="0,0,0,10"/><!-- 提交按鈕 --><Button Grid.Row="4" Grid.Column="1" Content="提交"Command="{Binding SubmitCommand}"Width="100"Height="30"HorizontalAlignment="Right"/>
</Grid>

9. 特殊文本框:PasswordBox與RichTextBox

除了標準的TextBox控件,WPF還提供了兩種特殊的文本輸入控件:PasswordBox和RichTextBox。它們都繼承自不同的基類,各自擁有獨特的功能和特性。

9.1 PasswordBox

PasswordBox專門用于輸入密碼,它會將用戶輸入的文本顯示為掩碼字符,保護敏感信息。

9.1.1 基本屬性
  • Password:獲取或設置密碼文本
  • PasswordChar:設置掩碼字符(默認為●)
  • MaxLength:設置可輸入的最大字符數
  • SecurePassword:以SecureString形式獲取密碼(更安全)
<PasswordBox x:Name="pwdBox"Password="初始密碼"PasswordChar="*"MaxLength="20"Width="200"Height="30"/>
// 獲取密碼
string password = pwdBox.Password;// 設置密碼
pwdBox.Password = "新密碼";// 修改掩碼字符
pwdBox.PasswordChar = '#';// 獲取安全密碼
System.Security.SecureString securePassword = pwdBox.SecurePassword;
9.1.2 事件處理

PasswordBox提供了PasswordChanged事件,在密碼內容變化時觸發:

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{PasswordBox passwordBox = sender as PasswordBox;// 顯示密碼長度passwordLength.Text = $"密碼長度:{passwordBox.Password.Length}";// 評估密碼強度PasswordStrength strength = EvaluatePasswordStrength(passwordBox.Password);UpdatePasswordStrengthIndicator(strength);
}private enum PasswordStrength { Weak, Medium, Strong }private PasswordStrength EvaluatePasswordStrength(string password)
{if (string.IsNullOrEmpty(password) || password.Length < 6)return PasswordStrength.Weak;bool hasUpperCase = password.Any(char.IsUpper);bool hasLowerCase = password.Any(char.IsLower);bool hasDigit = password.Any(char.IsDigit);bool hasSpecialChar = password.Any(c => !char.IsLetterOrDigit(c));if (password.Length >= 8 && hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar)return PasswordStrength.Strong;return PasswordStrength.Medium;
}private void UpdatePasswordStrengthIndicator(PasswordStrength strength)
{switch (strength){case PasswordStrength.Weak:strengthIndicator.Fill = new SolidColorBrush(Colors.Red);strengthText.Text = "弱";break;case PasswordStrength.Medium:strengthIndicator.Fill = new SolidColorBrush(Colors.Yellow);strengthText.Text = "中";break;case PasswordStrength.Strong:strengthIndicator.Fill = new SolidColorBrush(Colors.Green);strengthText.Text = "強";break;}
}
9.1.3 PasswordBox與數據綁定問題

出于安全考慮,PasswordBox的Password屬性沒有設計為依賴屬性,這意味著無法直接使用數據綁定。要實現數據綁定,可以采用以下方法:

  1. 使用附加屬性:
public static class PasswordBoxHelper
{public static readonly DependencyProperty BoundPasswordProperty =DependencyProperty.RegisterAttached("BoundPassword",typeof(string),typeof(PasswordBoxHelper),new FrameworkPropertyMetadata(string.Empty, OnBoundPasswordChanged));public static string GetBoundPassword(DependencyObject d){return (string)d.GetValue(BoundPasswordProperty);}public static void SetBoundPassword(DependencyObject d, string value){d.SetValue(BoundPasswordProperty, value);}private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){PasswordBox passwordBox = d as PasswordBox;if (passwordBox != null){passwordBox.Password = (string)e.NewValue;}}public static readonly DependencyProperty IsUpdatingProperty =DependencyProperty.RegisterAttached("IsUpdating",typeof(bool),typeof(PasswordBoxHelper));public static bool GetIsUpdating(DependencyObject d){return (bool)d.GetValue(IsUpdatingProperty);}public static void SetIsUpdating(DependencyObject d, bool value){d.SetValue(IsUpdatingProperty, value);}public static readonly DependencyProperty BindPassword =DependencyProperty.RegisterAttached("BindPassword",typeof(bool),typeof(PasswordBoxHelper),new PropertyMetadata(false, OnBindPasswordChanged));public static bool GetBindPassword(DependencyObject d){return (bool)d.GetValue(BindPassword);}public static void SetBindPassword(DependencyObject d, bool value){d.SetValue(BindPassword, value);}private static void OnBindPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){PasswordBox passwordBox = d as PasswordBox;if (passwordBox == null)return;if ((bool)e.NewValue){passwordBox.PasswordChanged += PasswordChanged;}else{passwordBox.PasswordChanged -= PasswordChanged;}}private static void PasswordChanged(object sender, RoutedEventArgs e){PasswordBox passwordBox = sender as PasswordBox;if (passwordBox == null)return;SetIsUpdating(passwordBox, true);SetBoundPassword(passwordBox, passwordBox.Password);SetIsUpdating(passwordBox, false);}
}

在XAML中使用:

<PasswordBox local:PasswordBoxHelper.BindPassword="True"local:PasswordBoxHelper.BoundPassword="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"Width="200"/>
  1. 使用行為(Behavior):
public class PasswordBoxBehavior : Behavior<PasswordBox>
{public static readonly DependencyProperty PasswordProperty =DependencyProperty.Register("Password", typeof(string), typeof(PasswordBoxBehavior),new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,OnPasswordPropertyChanged));private bool _isUpdating;public string Password{get { return (string)GetValue(PasswordProperty); }set { SetValue(PasswordProperty, value); }}protected override void OnAttached(){base.OnAttached();AssociatedObject.PasswordChanged += OnPasswordBoxPasswordChanged;}protected override void OnDetaching(){AssociatedObject.PasswordChanged -= OnPasswordBoxPasswordChanged;base.OnDetaching();}private static void OnPasswordPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){PasswordBoxBehavior behavior = d as PasswordBoxBehavior;if (behavior._isUpdating)return;if (behavior.AssociatedObject != null){behavior.AssociatedObject.Password = e.NewValue as string;}}private void OnPasswordBoxPasswordChanged(object sender, RoutedEventArgs e){_isUpdating = true;Password = AssociatedObject.Password;_isUpdating = false;}
}

在XAML中使用(需要引入Behaviors命名空間):

<PasswordBox Width="200"><i:Interaction.Behaviors><local:PasswordBoxBehavior Password="{Binding Password, Mode=TwoWay}"/></i:Interaction.Behaviors>
</PasswordBox>
9.1.4 安全最佳實踐

處理密碼時,應注意以下安全最佳實踐:

  1. 使用SecurePassword而不是Password屬性,避免明文密碼在內存中停留過長時間
  2. 避免將密碼保存在字符串變量中
  3. 密碼驗證后,及時清除內存中的密碼信息
  4. 使用合適的哈希算法和加鹽技術存儲密碼
// 推薦的密碼處理方式
private void Login_Click(object sender, RoutedEventArgs e)
{using (SecureString securePassword = pwdBox.SecurePassword){// 使用安全的方式驗證密碼bool isAuthenticated = AuthenticateUser(txtUsername.Text, securePassword);if (isAuthenticated){// 登錄成功處理}else{// 登錄失敗處理}}// 清除密碼框pwdBox.Clear();
}

9.2 RichTextBox

RichTextBox是一個強大的富文本編輯控件,它支持多種文本格式、樣式、顏色、嵌入對象等高級功能。它繼承自TextBoxBase類,但內部使用FlowDocument來存儲和管理內容。

9.2.1 基本用法

創建一個基本的RichTextBox控件:

<RichTextBox x:Name="richTextBox"Width="400"Height="300"VerticalScrollBarVisibility="Auto"><RichTextBox.Document><FlowDocument><Paragraph><Run Text="這是一個RichTextBox示例"/></Paragraph></FlowDocument></RichTextBox.Document>
</RichTextBox>
9.2.2 內容操作

RichTextBox的內容存儲在FlowDocument中,它由各種文檔元素(如Paragraph、Table、List等)組成。以下是一些常見的內容操作:

// 獲取文檔內容
FlowDocument document = richTextBox.Document;// 獲取純文本內容
TextRange textRange = new TextRange(document.ContentStart, document.ContentEnd);
string plainText = textRange.Text;// 設置文本內容
richTextBox.Document.Blocks.Clear();
richTextBox.Document.Blocks.Add(new Paragraph(new Run("新的文本內容")));// 追加文本
Paragraph lastParagraph = richTextBox.Document.Blocks.LastBlock as Paragraph;
if (lastParagraph != null)
{lastParagraph.Inlines.Add(new Run("追加的文本"));
}
else
{richTextBox.Document.Blocks.Add(new Paragraph(new Run("追加的文本")));
}
9.2.3 格式化文本

RichTextBox支持豐富的文本格式化功能:

// 創建格式化文本
Paragraph paragraph = new Paragraph();// 粗體文本
paragraph.Inlines.Add(new Bold(new Run("粗體文本")));
paragraph.Inlines.Add(new Run(" "));// 斜體文本
paragraph.Inlines.Add(new Italic(new Run("斜體文本")));
paragraph.Inlines.Add(new Run(" "));// 下劃線文本
paragraph.Inlines.Add(new Underline(new Run("下劃線文本")));
paragraph.Inlines.Add(new Run(" "));// 顏色文本
Run coloredText = new Run("彩色文本");
coloredText.Foreground = new SolidColorBrush(Colors.Red);
paragraph.Inlines.Add(coloredText);// 字體設置
Run fontText = new Run("不同字體文本");
fontText.FontFamily = new FontFamily("Arial");
fontText.FontSize = 16;
paragraph.Inlines.Add(new LineBreak());
paragraph.Inlines.Add(fontText);// 添加到文檔
richTextBox.Document.Blocks.Add(paragraph);
9.2.4 插入圖片和其他對象

RichTextBox還可以插入圖片和其他嵌入對象:

// 插入圖片
BitmapImage bitmap = new BitmapImage(new Uri("pack://application:,,,/Images/sample.png"));
Image image = new Image();
image.Source = bitmap;
image.Width = 100;
image.Height = 100;// 創建包含圖片的Inline元素
InlineUIContainer container = new InlineUIContainer(image);// 添加到文檔
Paragraph imageParagraph = new Paragraph();
imageParagraph.Inlines.Add(container);
richTextBox.Document.Blocks.Add(imageParagraph);
9.2.5 文件保存與加載

RichTextBox的內容可以保存為RTF、XAML或純文本格式:

// 保存為RTF
private void SaveAsRtf(RichTextBox richTextBox, string filePath)
{TextRange range = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);using (FileStream fileStream = new FileStream(filePath, FileMode.Create)){range.Save(fileStream, DataFormats.Rtf);}
}// 加載RTF文件
private void LoadRtfFile(RichTextBox richTextBox, string filePath)
{TextRange range = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);using (FileStream fileStream = new FileStream(filePath, FileMode.Open)){range.Load(fileStream, DataFormats.Rtf);}
}// 保存為XAML
private void SaveAsXaml(RichTextBox richTextBox, string filePath)
{TextRange range = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);using (FileStream fileStream = new FileStream(filePath, FileMode.Create)){range.Save(fileStream, DataFormats.Xaml);}
}// 加載XAML文件
private void LoadXamlFile(RichTextBox richTextBox, string filePath)
{TextRange range = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);using (FileStream fileStream = new FileStream(filePath, FileMode.Open)){range.Load(fileStream, DataFormats.Xaml);}
}// 保存為純文本
private void SaveAsText(RichTextBox richTextBox, string filePath)
{TextRange range = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);using (StreamWriter writer = new StreamWriter(filePath)){writer.Write(range.Text);}
}
9.2.6 創建簡單的文本編輯器

以下是一個簡單的富文本編輯器實現:

<Grid><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions><ToolBar Grid.Row="0"><Button Content="粗體" Click="Bold_Click"/><Button Content="斜體" Click="Italic_Click"/><Button Content="下劃線" Click="Underline_Click"/><Separator/><ComboBox x:Name="cmbFontSize" SelectedIndex="2" SelectionChanged="FontSize_Changed"><ComboBoxItem Content="10"/><ComboBoxItem Content="12"/><ComboBoxItem Content="14"/><ComboBoxItem Content="16"/><ComboBoxItem Content="18"/><ComboBoxItem Content="20"/></ComboBox></ToolBar><RichTextBox x:Name="editor" Grid.Row="1" Margin="10"/>
</Grid>
public partial class RichTextEditorWindow : Window
{public RichTextEditorWindow(){InitializeComponent();// 初始化字體列表foreach (FontFamily fontFamily in Fonts.SystemFontFamilies){cmbFontSize.Items.Add(fontFamily);}cmbFontSize.SelectedItem = editor.FontFamily;}private void Editor_SelectionChanged(object sender, RoutedEventArgs e){// 更新工具欄狀態UpdateToolbarState();}private void UpdateToolbarState(){// 獲取當前選擇的文本范圍TextRange selection = new TextRange(editor.Selection.Start, editor.Selection.End);// 更新加粗按鈕狀態object fontWeight = selection.GetPropertyValue(TextElement.FontWeightProperty);btnBold.IsChecked = (fontWeight != DependencyProperty.UnsetValue && (FontWeight)fontWeight == FontWeights.Bold);// 更新斜體按鈕狀態object fontStyle = selection.GetPropertyValue(TextElement.FontStyleProperty);btnItalic.IsChecked = (fontStyle != DependencyProperty.UnsetValue && (FontStyle)fontStyle == FontStyles.Italic);// 更新下劃線按鈕狀態object textDecorations = selection.GetPropertyValue(Inline.TextDecorationsProperty);btnUnderline.IsChecked = (textDecorations != DependencyProperty.UnsetValue && ((TextDecorationCollection)textDecorations).Equals(TextDecorations.Underline));// 更新對齊方式按鈕object textAlignment = selection.GetPropertyValue(Block.TextAlignmentProperty);if (textAlignment != DependencyProperty.UnsetValue){TextAlignment alignment = (TextAlignment)textAlignment;btnAlignLeft.IsChecked = (alignment == TextAlignment.Left);btnAlignCenter.IsChecked = (alignment == TextAlignment.Center);btnAlignRight.IsChecked = (alignment == TextAlignment.Right);btnAlignJustify.IsChecked = (alignment == TextAlignment.Justify);}// 更新字體和字號object fontFamily = selection.GetPropertyValue(TextElement.FontFamilyProperty);if (fontFamily != DependencyProperty.UnsetValue){cmbFontSize.SelectedItem = fontFamily;}object fontSize = selection.GetPropertyValue(TextElement.FontSizeProperty);if (fontSize != DependencyProperty.UnsetValue){string fontSizeStr = ((double)fontSize).ToString();foreach (ComboBoxItem item in cmbFontSize.Items){if (item.Content.ToString() == fontSizeStr){cmbFontSize.SelectedItem = item;break;}}}}private void FontSize_Changed(object sender, SelectionChangedEventArgs e){if (cmbFontSize.SelectedItem != null && editor.Selection.Start != editor.Selection.End){double fontSize = double.Parse((cmbFontSize.SelectedItem as ComboBoxItem).Content.ToString());editor.Selection.ApplyPropertyValue(TextElement.FontSizeProperty, fontSize);}}private void Bold_Click(object sender, RoutedEventArgs e){editor.Selection.ApplyPropertyValue(TextElement.FontWeightProperty, editor.Selection.GetPropertyValue(TextElement.FontWeightProperty).Equals(FontWeights.Bold) ? FontWeights.Normal : FontWeights.Bold);}private void Italic_Click(object sender, RoutedEventArgs e){editor.Selection.ApplyPropertyValue(TextElement.FontStyleProperty, editor.Selection.GetPropertyValue(TextElement.FontStyleProperty).Equals(FontStyles.Italic) ? FontStyles.Normal : FontStyles.Italic);}private void Underline_Click(object sender, RoutedEventArgs e){TextDecorationCollection decorations = editor.Selection.GetPropertyValue(Inline.TextDecorationsProperty) as TextDecorationCollection;if (decorations != null && decorations.Equals(TextDecorations.Underline)){editor.Selection.ApplyPropertyValue(Inline.TextDecorationsProperty, null);}else{editor.Selection.ApplyPropertyValue(Inline.TextDecorationsProperty, TextDecorations.Underline);}}
}

10. 樣式與模板定制

WPF的強大之處在于其樣式和模板系統,可以完全自定義控件的外觀而不改變其功能。TextBox控件同樣可以進行豐富的樣式和模板定制。

10.1 基本樣式設置

最簡單的樣式設置是通過直接設置TextBox的屬性:

<TextBox Width="200" Height="30"Background="LightYellow"Foreground="Navy"FontFamily="Consolas"FontSize="14"Padding="5,3"BorderBrush="Orange"BorderThickness="1"TextAlignment="Center"/>

10.2 使用Style對象

更系統的方法是創建Style對象:

<Window.Resources><Style x:Key="CustomTextBoxStyle" TargetType="TextBox"><Setter Property="Background" Value="LightYellow"/><Setter Property="Foreground" Value="Navy"/><Setter Property="FontFamily" Value="Consolas"/><Setter Property="FontSize" Value="14"/><Setter Property="Padding" Value="5,3"/><Setter Property="BorderBrush" Value="Orange"/><Setter Property="BorderThickness" Value="1"/><Setter Property="TextAlignment" Value="Center"/><Setter Property="Height" Value="30"/></Style>
</Window.Resources><TextBox Style="{StaticResource CustomTextBoxStyle}" Width="200"/>

10.3 使用觸發器

樣式可以包含觸發器,根據條件改變控件的外觀:

<Style x:Key="ValidationTextBoxStyle" TargetType="TextBox"><Setter Property="Background" Value="White"/><Setter Property="BorderBrush" Value="Gray"/><Setter Property="BorderThickness" Value="1"/><Style.Triggers><!-- 獲得焦點時改變邊框 --><Trigger Property="IsFocused" Value="True"><Setter Property="BorderBrush" Value="#3498db"/><Setter Property="BorderThickness" Value="2"/></Trigger><!-- 只讀狀態下改變背景色 --><Trigger Property="IsReadOnly" Value="True"><Setter Property="Background" Value="LightGray"/></Trigger><!-- 當文本為空時顯示灰色背景 --><Trigger Property="Text" Value=""><Setter Property="Background" Value="#FFEEEEEE"/></Trigger><!-- 當文本長度超過10時顯示警告顏色 --><DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text.Length}" Value="10"><Setter Property="Foreground" Value="Red"/><Setter Property="ToolTip" Value="文本長度已達到10個字符"/></DataTrigger></Style.Triggers>
</Style>

10.4 樣式繼承

可以通過BasedOn屬性繼承現有樣式:

<Style x:Key="BaseTextBoxStyle" TargetType="TextBox"><Setter Property="FontFamily" Value="Segoe UI"/><Setter Property="FontSize" Value="12"/><Setter Property="Padding" Value="5,3"/>
</Style><Style x:Key="SpecialTextBoxStyle" TargetType="TextBox" BasedOn="{StaticResource BaseTextBoxStyle}"><Setter Property="Background" Value="LightBlue"/><Setter Property="Foreground" Value="DarkBlue"/>
</Style>

10.5 隱式樣式

可以創建針對所有TextBox的隱式樣式,無需顯式引用:

<Style TargetType="TextBox"><Setter Property="Padding" Value="5,3"/><Setter Property="Margin" Value="0,0,0,5"/><Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>

10.6 控件模板定制

要完全控制TextBox的外觀,可以自定義其控件模板:

<Style x:Key="ModernTextBoxStyle" TargetType="TextBox"><Setter Property="Background" Value="#F5F5F5"/><Setter Property="Foreground" Value="#333333"/><Setter Property="BorderThickness" Value="0,0,0,1"/><Setter Property="BorderBrush" Value="#CCCCCC"/><Setter Property="Padding" Value="5,8"/><Setter Property="FontSize" Value="13"/><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="TextBox"><Grid><!-- 背景和邊框 --><Border x:Name="border" Background="{TemplateBinding Background}"BorderBrush="{TemplateBinding BorderBrush}"BorderThickness="{TemplateBinding BorderThickness}"><Grid><!-- 水印文本 --><TextBlock x:Name="PART_Watermark"Text="請輸入內容..."Foreground="#AAAAAA"Visibility="Collapsed"Margin="{TemplateBinding Padding}"VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/><!-- 實際的文本編輯區域 --><ScrollViewer x:Name="PART_ContentHost"/></Grid></Border></Grid><ControlTemplate.Triggers><!-- 顯示水印 --><MultiTrigger><MultiTrigger.Conditions><Condition Property="Text" Value=""/><Condition Property="IsFocused" Value="False"/></MultiTrigger.Conditions><Setter TargetName="PART_Watermark" Property="Visibility" Value="Visible"/></MultiTrigger><!-- 獲取焦點時的樣式 --><Trigger Property="IsFocused" Value="True"><Trigger.EnterActions><BeginStoryboard><Storyboard><ColorAnimation Storyboard.TargetName="border"Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)"To="#3498db"Duration="0:0:0.2"/><ThicknessAnimation Storyboard.TargetName="border"Storyboard.TargetProperty="BorderThickness"To="0,0,0,2"Duration="0:0:0.2"/></Storyboard></BeginStoryboard></Trigger.EnterActions><Trigger.ExitActions><BeginStoryboard><Storyboard><ColorAnimation Storyboard.TargetName="border"Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)"To="#CCCCCC"Duration="0:0:0.2"/><ThicknessAnimation Storyboard.TargetName="border"Storyboard.TargetProperty="BorderThickness"To="0,0,0,1"Duration="0:0:0.2"/></Storyboard></BeginStoryboard></Trigger.ExitActions></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter>
</Style>

10.7 自定義附加屬性

可以創建附加屬性來擴展TextBox的功能:

public static class TextBoxExtensions
{// 定義水印文本附加屬性public static readonly DependencyProperty WatermarkProperty =DependencyProperty.RegisterAttached("Watermark",typeof(string),typeof(TextBoxExtensions),new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender));// Getterpublic static string GetWatermark(DependencyObject obj){return (string)obj.GetValue(WatermarkProperty);}// Setterpublic static void SetWatermark(DependencyObject obj, string value){obj.SetValue(WatermarkProperty, value);}// 定義是否顯示清除按鈕的附加屬性public static readonly DependencyProperty HasClearButtonProperty =DependencyProperty.RegisterAttached("HasClearButton",typeof(bool),typeof(TextBoxExtensions),new FrameworkPropertyMetadata(false, OnHasClearButtonChanged));// Getterpublic static bool GetHasClearButton(DependencyObject obj){return (bool)obj.GetValue(HasClearButtonProperty);}// Setterpublic static void SetHasClearButton(DependencyObject obj, bool value){obj.SetValue(HasClearButtonProperty, value);}// 當HasClearButton屬性改變時處理private static void OnHasClearButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){if (d is TextBox textBox){if ((bool)e.NewValue){// 添加清除按鈕textBox.Loaded += TextBox_Loaded;}else{// 移除清除按鈕textBox.Loaded -= TextBox_Loaded;}}}private static void TextBox_Loaded(object sender, RoutedEventArgs e){if (sender is TextBox textBox){// 為TextBox創建一個父容器var parent = textBox.Parent;if (parent is Grid || parent is Canvas || parent is Panel){var container = parent;var index = -1;if (parent is Grid){index = Grid.GetColumn(textBox);}// 創建清除按鈕var button = new Button{Content = "X",Width = 16,Height = 16,Padding = new Thickness(0),Margin = new Thickness(0),VerticalAlignment = VerticalAlignment.Center,HorizontalAlignment = HorizontalAlignment.Right,Background = Brushes.Transparent,BorderThickness = new Thickness(0),Visibility = string.IsNullOrEmpty(textBox.Text) ? Visibility.Collapsed : Visibility.Visible};// 處理按鈕點擊事件button.Click += (s, args) =>{textBox.Clear();textBox.Focus();};// 處理文本變化事件textBox.TextChanged += (s, args) =>{button.Visibility = string.IsNullOrEmpty(textBox.Text) ? Visibility.Collapsed : Visibility.Visible;};// 將按鈕添加到父容器并定位if (parent is Grid grid){grid.Children.Add(button);Grid.SetRow(button, Grid.GetRow(textBox));Grid.SetColumn(button, Grid.GetColumn(textBox));}else if (parent is Canvas canvas){canvas.Children.Add(button);Canvas.SetLeft(button, Canvas.GetLeft(textBox) + textBox.ActualWidth - button.Width - 5);Canvas.SetTop(button, Canvas.GetTop(textBox) + (textBox.ActualHeight - button.Height) / 2);}else if (parent is Panel panel){panel.Children.Add(button);}}}}
}

在XAML中使用附加屬性:

<TextBox local:TextBoxExtensions.Watermark="請輸入搜索內容"local:TextBoxExtensions.HasClearButton="True"Width="200"/>

10.8 自定義控件

創建自定義的TextBox派生控件,可以添加新功能:

public class WatermarkTextBox : TextBox
{static WatermarkTextBox(){DefaultStyleKeyProperty.OverrideMetadata(typeof(WatermarkTextBox), new FrameworkPropertyMetadata(typeof(WatermarkTextBox)));}public static readonly DependencyProperty WatermarkProperty =DependencyProperty.Register("Watermark", typeof(string), typeof(WatermarkTextBox),new PropertyMetadata(string.Empty));public string Watermark{get { return (string)GetValue(WatermarkProperty); }set { SetValue(WatermarkProperty, value); }}public static readonly DependencyProperty WatermarkColorProperty =DependencyProperty.Register("WatermarkColor", typeof(Brush), typeof(WatermarkTextBox),new PropertyMetadata(Brushes.Gray));public Brush WatermarkColor{get { return (Brush)GetValue(WatermarkColorProperty); }set { SetValue(WatermarkColorProperty, value); }}
}

創建對應的控件模板:

<Style TargetType="{x:Type local:WatermarkTextBox}"><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type local:WatermarkTextBox}"><Border Background="{TemplateBinding Background}"BorderBrush="{TemplateBinding BorderBrush}"BorderThickness="{TemplateBinding BorderThickness}"><Grid><TextBlock x:Name="WatermarkText"Text="{TemplateBinding Watermark}"Foreground="{TemplateBinding WatermarkColor}"Visibility="Collapsed"Padding="{TemplateBinding Padding}"VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/><ScrollViewer x:Name="PART_ContentHost"/></Grid></Border><ControlTemplate.Triggers><MultiTrigger><MultiTrigger.Conditions><Condition Property="Text" Value=""/><Condition Property="IsFocused" Value="False"/></MultiTrigger.Conditions><Setter TargetName="WatermarkText" Property="Visibility" Value="Visible"/></MultiTrigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter>
</Style>

10.9 使用第三方控件庫

許多第三方控件庫提供了增強的TextBox控件,例如:

  • MahApps.Metro - MetroTextBox
  • MaterialDesignInXAML - MaterialDesignTextBox
  • ModernWPF - 現代風格TextBox
  • DevExpress - TextEdit
  • Telerik - RadTextBox
  • Syncfusion - SfTextBoxExt

10.10 動態樣式效果

以下是一個動態效果的TextBox樣式示例:

<Style x:Key="AnimatedTextBoxStyle" TargetType="TextBox"><Setter Property="BorderThickness" Value="0,0,0,1"/><Setter Property="BorderBrush" Value="#CCCCCC"/><Setter Property="Background" Value="Transparent"/><Setter Property="Padding" Value="5,3"/><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="TextBox"><Grid><Border x:Name="border" Background="{TemplateBinding Background}"BorderBrush="{TemplateBinding BorderBrush}"BorderThickness="{TemplateBinding BorderThickness}"><ScrollViewer x:Name="PART_ContentHost"/></Border></Grid><ControlTemplate.Triggers><Trigger Property="IsFocused" Value="True"><Trigger.EnterActions><BeginStoryboard><Storyboard><ColorAnimation Storyboard.TargetName="border"Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)"To="#3498db"Duration="0:0:0.2"/><ThicknessAnimation Storyboard.TargetName="border"Storyboard.TargetProperty="BorderThickness"To="0,0,0,2"Duration="0:0:0.2"/></Storyboard></BeginStoryboard></Trigger.EnterActions><Trigger.ExitActions><BeginStoryboard><Storyboard><ColorAnimation Storyboard.TargetName="border"Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)"To="#CCCCCC"Duration="0:0:0.2"/><ThicknessAnimation Storyboard.TargetName="border"Storyboard.TargetProperty="BorderThickness"To="0,0,0,1"Duration="0:0:0.2"/></Storyboard></BeginStoryboard></Trigger.ExitActions></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter>
</Style>

11. 常見應用場景

TextBox控件在各種應用場景中都有廣泛的應用。以下是一些常見的應用場景及其實現方式。

11.1 登錄表單

登錄表單是最常見的應用場景之一,通常包含用戶名TextBox和密碼PasswordBox:

<StackPanel Width="300" Margin="20"><TextBlock Text="用戶登錄" FontSize="18" FontWeight="Bold" Margin="0,0,0,20"/><TextBlock Text="用戶名:" Margin="0,0,0,5"/><TextBox x:Name="txtUsername" Margin="0,0,0,15"/><TextBlock Text="密碼:" Margin="0,0,0,5"/><PasswordBox x:Name="txtPassword" Margin="0,0,0,20"/><Button Content="登錄" Click="Login_Click" HorizontalAlignment="Right" Padding="15,5"/>
</StackPanel>
private void Login_Click(object sender, RoutedEventArgs e)
{string username = txtUsername.Text;string password = txtPassword.Password;// 驗證輸入if (string.IsNullOrEmpty(username)){MessageBox.Show("請輸入用戶名");txtUsername.Focus();return;}if (string.IsNullOrEmpty(password)){MessageBox.Show("請輸入密碼");txtPassword.Focus();return;}// 進行登錄驗證bool isValid = AuthenticateUser(username, password);if (isValid){// 登錄成功處理}else{// 登錄失敗處理}
}private bool AuthenticateUser(string username, string password)
{// 實際應用中,這里應該與服務器進行身份驗證// 這里僅作為示例return username == "admin" && password == "password";
}

11.2 搜索框

帶有清除按鈕和水印的搜索框:

<Grid><TextBox x:Name="txtSearch" Width="300" Height="30"Padding="5,0,25,0"VerticalContentAlignment="Center"TextChanged="SearchTextBox_TextChanged"/><TextBlock x:Name="watermark"Text="輸入關鍵詞搜索..."IsHitTestVisible="False"Foreground="Gray"Margin="8,0,0,0"VerticalAlignment="Center"/><Button x:Name="btnClear"Content="?"Width="20"Height="20"HorizontalAlignment="Right"Margin="0,0,5,0"Background="Transparent"BorderThickness="0"Visibility="Collapsed"Click="ClearButton_Click"/>
</Grid>
private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
{// 更新水印可見性watermark.Visibility = string.IsNullOrEmpty(txtSearch.Text) ? Visibility.Visible : Visibility.Collapsed;// 更新清除按鈕可見性btnClear.Visibility = string.IsNullOrEmpty(txtSearch.Text) ? Visibility.Collapsed : Visibility.Visible;// 執行搜索PerformSearch(txtSearch.Text);
}private void ClearButton_Click(object sender, RoutedEventArgs e)
{txtSearch.Clear();txtSearch.Focus();
}private void PerformSearch(string searchTerm)
{if (string.IsNullOrEmpty(searchTerm)){// 如果搜索詞為空,顯示所有項目}else{// 根據搜索詞過濾項目}
}

11.3 富文本編輯器

用RichTextBox實現一個簡單的編輯器:

<Grid><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions><ToolBar Grid.Row="0"><Button Content="粗體" Click="Bold_Click"/><Button Content="斜體" Click="Italic_Click"/><Button Content="下劃線" Click="Underline_Click"/><Separator/><ComboBox x:Name="cmbFontSize" SelectedIndex="2" SelectionChanged="FontSize_Changed"><ComboBoxItem Content="10"/><ComboBoxItem Content="12"/><ComboBoxItem Content="14"/><ComboBoxItem Content="16"/><ComboBoxItem Content="18"/><ComboBoxItem Content="20"/></ComboBox></ToolBar><RichTextBox x:Name="editor" Grid.Row="1" SelectionChanged="Editor_SelectionChanged"VerticalScrollBarVisibility="Auto"AcceptsTab="True"SpellCheck.IsEnabled="True"/>
</Grid>
public partial class RichTextEditorWindow : Window
{public RichTextEditorWindow(){InitializeComponent();// 初始化字體列表foreach (FontFamily fontFamily in Fonts.SystemFontFamilies){cmbFontSize.Items.Add(fontFamily);}cmbFontSize.SelectedItem = editor.FontFamily;}private void Editor_SelectionChanged(object sender, RoutedEventArgs e){// 更新工具欄狀態UpdateToolbarState();}private void UpdateToolbarState(){// 獲取當前選擇的文本范圍TextRange selection = new TextRange(editor.Selection.Start, editor.Selection.End);// 更新加粗按鈕狀態object fontWeight = selection.GetPropertyValue(TextElement.FontWeightProperty);btnBold.IsChecked = (fontWeight != DependencyProperty.UnsetValue && (FontWeight)fontWeight == FontWeights.Bold);// 更新斜體按鈕狀態object fontStyle = selection.GetPropertyValue(TextElement.FontStyleProperty);btnItalic.IsChecked = (fontStyle != DependencyProperty.UnsetValue && (FontStyle)fontStyle == FontStyles.Italic);// 更新下劃線按鈕狀態object textDecorations = selection.GetPropertyValue(Inline.TextDecorationsProperty);btnUnderline.IsChecked = (textDecorations != DependencyProperty.UnsetValue && ((TextDecorationCollection)textDecorations).Equals(TextDecorations.Underline));// 更新對齊方式按鈕object textAlignment = selection.GetPropertyValue(Block.TextAlignmentProperty);if (textAlignment != DependencyProperty.UnsetValue){TextAlignment alignment = (TextAlignment)textAlignment;btnAlignLeft.IsChecked = (alignment == TextAlignment.Left);btnAlignCenter.IsChecked = (alignment == TextAlignment.Center);btnAlignRight.IsChecked = (alignment == TextAlignment.Right);btnAlignJustify.IsChecked = (alignment == TextAlignment.Justify);}// 更新字體和字號object fontFamily = selection.GetPropertyValue(TextElement.FontFamilyProperty);if (fontFamily != DependencyProperty.UnsetValue){cmbFontSize.SelectedItem = fontFamily;}object fontSize = selection.GetPropertyValue(TextElement.FontSizeProperty);if (fontSize != DependencyProperty.UnsetValue){string fontSizeStr = ((double)fontSize).ToString();foreach (ComboBoxItem item in cmbFontSize.Items){if (item.Content.ToString() == fontSizeStr){cmbFontSize.SelectedItem = item;break;}}}}private void FontSize_Changed(object sender, SelectionChangedEventArgs e){if (cmbFontSize.SelectedItem != null && editor.Selection.Start != editor.Selection.End){ComboBoxItem selectedItem = (ComboBoxItem)cmbFontSize.SelectedItem;editor.Selection.ApplyPropertyValue(TextElement.FontSizeProperty, double.Parse(selectedItem.Content.ToString()));}}private void Bold_Click(object sender, RoutedEventArgs e){if (btnBold.IsChecked == true){editor.Selection.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold);}else{editor.Selection.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Normal);}editor.Focus();}private void Italic_Click(object sender, RoutedEventArgs e){if (btnItalic.IsChecked == true){editor.Selection.ApplyPropertyValue(TextElement.FontStyleProperty, FontStyles.Italic);}else{editor.Selection.ApplyPropertyValue(TextElement.FontStyleProperty, FontStyles.Normal);}editor.Focus();}private void Underline_Click(object sender, RoutedEventArgs e){if (btnUnderline.IsChecked == true){editor.Selection.ApplyPropertyValue(Inline.TextDecorationsProperty, TextDecorations.Underline);}else{TextDecorationCollection emptyDecorations = new TextDecorationCollection();editor.Selection.ApplyPropertyValue(Inline.TextDecorationsProperty, emptyDecorations);}editor.Focus();}
}

11.4 數值輸入控件

限制只能輸入數字的TextBox:

<TextBox x:Name="numericTextBox"PreviewTextInput="NumericTextBox_PreviewTextInput"PreviewKeyDown="NumericTextBox_PreviewKeyDown"TextChanged="NumericTextBox_TextChanged"Width="100"/>
private void NumericTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{// 檢查輸入是否為數字或小數點Regex regex = new Regex("[^0-9.]+");e.Handled = regex.IsMatch(e.Text);
}private void NumericTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{// 禁止輸入空格if (e.Key == Key.Space){e.Handled = true;}
}private void NumericTextBox_TextChanged(object sender, TextChangedEventArgs e)
{TextBox textBox = sender as TextBox;// 驗證文本是否為有效的數字格式if (!string.IsNullOrEmpty(textBox.Text)){double value;if (!double.TryParse(textBox.Text, out value)){// 如果不是有效的數字,恢復為上一個有效值textBox.Text = _lastValidValue;textBox.CaretIndex = textBox.Text.Length;}else{_lastValidValue = textBox.Text;}}else{_lastValidValue = string.Empty;}
}private string _lastValidValue = string.Empty;

11.5 自動完成文本框

實現帶有自動完成功能的TextBox:

<Grid><TextBox x:Name="txtAutoComplete"Width="200"Height="30"TextChanged="AutoComplete_TextChanged"KeyDown="AutoComplete_KeyDown"/><Popup x:Name="autoCompletePopup"PlacementTarget="{Binding ElementName=txtAutoComplete}"Placement="Bottom"Width="{Binding ElementName=txtAutoComplete, Path=ActualWidth}"IsOpen="False"StaysOpen="False"><ListBox x:Name="suggestionsList"MaxHeight="200"SelectionChanged="SuggestionsList_SelectionChanged"/></Popup>
</Grid>
// 示例數據源
private List<string> _dataSource = new List<string>
{"蘋果", "香蕉", "橙子", "草莓", "葡萄", "西瓜", "菠蘿", "芒果", "櫻桃", "藍莓","檸檬", "石榴", "獼猴桃", "荔枝", "龍眼"
};private void AutoComplete_TextChanged(object sender, TextChangedEventArgs e)
{string input = txtAutoComplete.Text.Trim();if (string.IsNullOrEmpty(input)){autoCompletePopup.IsOpen = false;return;}// 根據輸入文本篩選匹配項var suggestions = _dataSource.Where(i => i.Contains(input, StringComparison.OrdinalIgnoreCase)).ToList();if (suggestions.Count > 0){// 更新建議列表suggestionsList.ItemsSource = suggestions;autoCompletePopup.IsOpen = true;}else{autoCompletePopup.IsOpen = false;}
}private void AutoComplete_KeyDown(object sender, KeyEventArgs e)
{if (!autoCompletePopup.IsOpen)return;// 用上下鍵導航建議列表if (e.Key == Key.Down){if (suggestionsList.SelectedIndex < suggestionsList.Items.Count - 1){suggestionsList.SelectedIndex++;}e.Handled = true;}else if (e.Key == Key.Up){if (suggestionsList.SelectedIndex > 0){suggestionsList.SelectedIndex--;}e.Handled = true;}else if (e.Key == Key.Enter){// 選擇當前選中的建議項if (suggestionsList.SelectedItem != null){txtAutoComplete.Text = suggestionsList.SelectedItem.ToString();txtAutoComplete.CaretIndex = txtAutoComplete.Text.Length;autoCompletePopup.IsOpen = false;e.Handled = true;}}else if (e.Key == Key.Escape){// 關閉建議列表autoCompletePopup.IsOpen = false;e.Handled = true;}
}private void SuggestionsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{if (suggestionsList.SelectedItem != null){txtAutoComplete.Text = suggestionsList.SelectedItem.ToString();txtAutoComplete.CaretIndex = txtAutoComplete.Text.Length;autoCompletePopup.IsOpen = false;}
}

11.6 配置文本編輯器

自定義配置文件編輯器:

<Grid><Grid.RowDefinitions><RowDefinition Height="*"/><RowDefinition Height="Auto"/></Grid.RowDefinitions><ToolBar Grid.Row="0"><Button Content="粗體" Click="Bold_Click"/><Button Content="斜體" Click="Italic_Click"/><Button Content="下劃線" Click="Underline_Click"/><Separator/><ComboBox x:Name="cmbFontSize" SelectedIndex="2" SelectionChanged="FontSize_Changed"><ComboBoxItem Content="10"/><ComboBoxItem Content="12"/><ComboBoxItem Content="14"/><ComboBoxItem Content="16"/><ComboBoxItem Content="18"/><ComboBoxItem Content="20"/></ComboBox></ToolBar><RichTextBox x:Name="editor" Grid.Row="1" SelectionChanged="Editor_SelectionChanged"VerticalScrollBarVisibility="Auto"AcceptsTab="True"SpellCheck.IsEnabled="True"/>
</Grid>
public partial class RichTextEditorWindow : Window
{public RichTextEditorWindow(){InitializeComponent();// 初始化字體列表foreach (FontFamily fontFamily in Fonts.SystemFontFamilies){cmbFontSize.Items.Add(fontFamily);}cmbFontSize.SelectedItem = editor.FontFamily;}private void Editor_SelectionChanged(object sender, RoutedEventArgs e){// 更新工具欄狀態UpdateToolbarState();}private void UpdateToolbarState(){// 獲取當前選擇的文本范圍TextRange selection = new TextRange(editor.Selection.Start, editor.Selection.End);// 更新加粗按鈕狀態object fontWeight = selection.GetPropertyValue(TextElement.FontWeightProperty);btnBold.IsChecked = (fontWeight != DependencyProperty.UnsetValue && (FontWeight)fontWeight == FontWeights.Bold);// 更新斜體按鈕狀態object fontStyle = selection.GetPropertyValue(TextElement.FontStyleProperty);btnItalic.IsChecked = (fontStyle != DependencyProperty.UnsetValue && (FontStyle)fontStyle == FontStyles.Italic);// 更新下劃線按鈕狀態object textDecorations = selection.GetPropertyValue(Inline.TextDecorationsProperty);btnUnderline.IsChecked = (textDecorations != DependencyProperty.UnsetValue && ((TextDecorationCollection)textDecorations).Equals(TextDecorations.Underline));// 更新對齊方式按鈕object textAlignment = selection.GetPropertyValue(Block.TextAlignmentProperty);if (textAlignment != DependencyProperty.UnsetValue){TextAlignment alignment = (TextAlignment)textAlignment;btnAlignLeft.IsChecked = (alignment == TextAlignment.Left);btnAlignCenter.IsChecked = (alignment == TextAlignment.Center);btnAlignRight.IsChecked = (alignment == TextAlignment.Right);btnAlignJustify.IsChecked = (alignment == TextAlignment.Justify);}// 更新字體和字號object fontFamily = selection.GetPropertyValue(TextElement.FontFamilyProperty);if (fontFamily != DependencyProperty.UnsetValue){cmbFontSize.SelectedItem = fontFamily;}object fontSize = selection.GetPropertyValue(TextElement.FontSizeProperty);if (fontSize != DependencyProperty.UnsetValue){string fontSizeStr = ((double)fontSize).ToString();foreach (ComboBoxItem item in cmbFontSize.Items){if (item.Content.ToString() == fontSizeStr){cmbFontSize.SelectedItem = item;break;}}}}private void FontSize_Changed(object sender, SelectionChangedEventArgs e){if (cmbFontSize.SelectedItem != null && editor.Selection.Start != editor.Selection.End){ComboBoxItem selectedItem = (ComboBoxItem)cmbFontSize.SelectedItem;editor.Selection.ApplyPropertyValue(TextElement.FontSizeProperty, double.Parse(selectedItem.Content.ToString()));}}private void Bold_Click(object sender, RoutedEventArgs e){if (btnBold.IsChecked == true){editor.Selection.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold);}else{editor.Selection.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Normal);}editor.Focus();}private void Italic_Click(object sender, RoutedEventArgs e){if (btnItalic.IsChecked == true){editor.Selection.ApplyPropertyValue(TextElement.FontStyleProperty, FontStyles.Italic);}else{editor.Selection.ApplyPropertyValue(TextElement.FontStyleProperty, FontStyles.Normal);}editor.Focus();}private void Underline_Click(object sender, RoutedEventArgs e){if (btnUnderline.IsChecked == true){editor.Selection.ApplyPropertyValue(Inline.TextDecorationsProperty, TextDecorations.Underline);}else{TextDecorationCollection emptyDecorations = new TextDecorationCollection();editor.Selection.ApplyPropertyValue(Inline.TextDecorationsProperty, emptyDecorations);}editor.Focus();}
}

12. 總結與最佳實踐

TextBox控件是WPF中常用的文本輸入控件,提供了豐富的功能和靈活的定制選項。在使用TextBox時,應該注意以下幾點:

  • 根據實際需求選擇合適的文本框類型(單行或多行)
  • 合理設置文本框的屬性,如MaxLength、AcceptsReturn、AcceptsTab等
  • 注意文本框的樣式和模板定制,以提高用戶體驗
  • 在數據驗證和撤銷/重做功能方面,應該謹慎處理,以確保數據安全和用戶操作的便利性

通過本文的介紹和示例,開發者可以充分利用TextBox控件構建優秀的用戶界面,并根據實際需求進行靈活的定制和擴展。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/79083.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/79083.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/79083.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

1.4 點云數據獲取方式——結構光相機

圖1-4-1結構光相機 結構光相機作為獲取三維點云數據的關鍵設備,其工作原理基于主動式測量技術。通過投射已知圖案,如條紋、點陣、格雷碼等,至物體表面,這些圖案會因物體表面的高度變化而發生變形。與此同時,利用相機從特定

【MATLAB第118期】基于MATLAB的雙通道CNN多輸入單輸出分類預測方法

【MATLAB第118期】基于MATLAB的雙通道CNN多輸入單輸出分類預測方法 一、雙通道CNN簡介 在深度學習領域&#xff0c;卷積神經網絡&#xff08;CNN&#xff09;憑借其強大的特征提取能力&#xff0c;已成為圖像識別、自然語言處理等任務的核心技術。傳統單通道CNN在處理單一模態…

2025上海車展 | 移遠通信推出自研NG-eCall QuecOpen方案,助力汽車安全新標準加速落地

4月29日&#xff0c;在2025上海國際汽車工業展覽會期間&#xff0c;全球領先的物聯網和車聯網整體解決方案供應商移遠通信宣布&#xff0c;正式發布自主研發的NG-eCall&#xff08;下一代緊急呼叫系統&#xff09;QuecOpen解決方案。 該方案憑借高度集成的軟硬件協同設計&…

leetcode76

目錄 803ms超時。。。。越改越超時。。。 一些糾纏 代碼分析&#xff1a; 代碼問題&#xff1a; 改進建議&#xff1a; 示例代碼&#xff1a; The error message you’re seeing indicates that there is a reference binding to a null pointer in your code. This typ…

大數據應用開發和項目實戰-Seaborn

一、Seaborn概述 Seaborn是基于Python數據可視化庫Matplotlib開發的擴展庫&#xff0c;專注于統計圖形的繪制&#xff0c;旨在通過簡潔的代碼實現復雜數據的可視化&#xff0c;幫助用戶更輕松地呈現和理解數據。其核心設計目標是簡化統計可視化流程&#xff0c;提供高級接口和美…

數據科學與計算

Seaborn的介紹 Seaborn 是一個建立在 Matplotlib 基礎之上的 Python 數據可視化庫&#xff0c;專注于繪制各種統計圖形&#xff0c;以便更輕松地呈現和理解數據。 Seaborn 的設計目標是簡化統計數據可視化的過程&#xff0c;提供高級接口和美觀的默認主題&#xff0c;使得用戶…

深入淺出循環神經網絡(RNN):原理、應用與實戰

1、引言 在深度學習領域&#xff0c;循環神經網絡&#xff08;Recurrent Neural Network, RNN&#xff09;是一種專門用于處理**序列數據**的神經網絡架構。與傳統的前饋神經網絡不同&#xff0c;RNN 具有**記憶能力**&#xff0c;能夠捕捉數據中的時間依賴性&#xff0c;廣泛應…

廣州創科——湖北房縣汪家河水庫除險加固信息化工程

汪家河水庫 汪家河水庫位于湖北省房縣&#xff0c;建于1971年&#xff0c;其地利可謂是天公之作&#xff0c;東西二山蜿蜒起伏&#xff0c;山峰相連&#xff0c;峰峰比高&#xff0c;無有盡頭&#xff0c;東邊陡峭&#xff0c;西邊相對平坦&#xff0c;半山腰有一條鄉村道路&am…

C++日更八股--day2

### C sort 的底層原理 這里其實原來問的是你如何優化快速排序&#xff0c;但是我最初只以為是隨機選擇基準&#xff0c;但是很顯然面試官對此并不滿意 閑暇之際&#xff0c;看到一篇介紹sort的原理的文章&#xff0c;才知道原來如是也 1.快速排序&#xff1a;作為主要算法&…

UniApp 的現狀與 WASM 支持的迫切性

UniApp 的現狀與 WASM 支持的迫切性 點擊進入免費1 UniApp 的現狀與 WASM 支持的迫切性 點擊進入免費版2 一、UniApp 的跨平臺優勢與性能瓶頸 UniApp 憑借“一次開發,多端發布”的核心理念,已成為跨平臺開發的主流框架之一。然而,隨著移動應用場景的復雜化(如 3D 渲染、音…

如何正確使用日程表

日程安排&#xff0c;是時間管理中非常重要的一項&#xff0c;也是不容易管好的一項。 日程安排&#xff0c;通常指放到日程表里的事情&#xff0c;一般來說&#xff0c;放到日程表的事情要符合以下幾個特點&#xff1a; 01.明確具體時間段&#xff0c;比如是下午2點到下午三…

【Token系列】14|Prompt不是文本,是token結構工程

文章目錄 14&#xff5c;Prompt不是文本&#xff0c;是token結構工程一、很多人寫的是“自然語言”&#xff0c;模型讀的是“token序列”二、Prompt寫法會直接影響token結構密度三、token分布影響Attention矩陣的聚焦方式四、token數 ≠ 有效信息量五、Prompt結構設計建議&…

研發效率破局之道閱讀總結(4)個人效率

研發效率破局之道閱讀總結(4)個人效率 Author: Once Day Date: 2025年4月30日 一位熱衷于Linux學習和開發的菜鳥&#xff0c;試圖譜寫一場冒險之旅&#xff0c;也許終點只是一場白日夢… 漫漫長路&#xff0c;有人對你微笑過嘛… 全系列文章可參考專欄: 程序的藝術_Once-Day…

CNN代碼詳細注釋

import torch from torch import nn#定義張量x&#xff0c;它的尺寸是5x1x28x28 #表示了5個單通道28x28大小的數據 xtorch.zeros([5,1,28,28])#定義一個輸入通道是1&#xff0c;輸出通道是6&#xff0c;卷積核大小是5x5的卷積層 convnn.Conv2d(in_channels1,out_channels6,ker…

機器指標監控技術方案

文章目錄 機器指標監控技術方案架構圖組件簡介Prometheus 簡介核心特性適用場景 Grafana 簡介核心特性適用場景 Alertmanager 簡介核心特性適用場景 數據采集機器Node ExporterMySQL ExporterRedis ExporterES ExporterRocketMQ ExporterSpringcloud ExporterNacos 數據存儲短期…

【Office-Excel】單元格輸入數據后自動填充單位

1.自定義設置單元格格式 例如我想輸入數字10&#xff0c;回車確認后自動顯示10kg。 右擊單元格或者快捷鍵&#xff08;Ctrl1&#xff09;&#xff0c;選擇設置單元格格式&#xff0c;自定義格式輸入&#xff1a; 0"kg"格式仍是數字&#xff0c;但是顯示是10kg&…

JavaScript的3D庫有哪些?

JavaScript的3D庫有哪些&#xff1f; 在3D開發領域&#xff0c;JavaScript提供了多種庫和框架&#xff0c;使開發者能夠在瀏覽器中創建豐富的3D體驗。以下是一些流行的3D方面的JavaScript庫&#xff1a; Three.js&#xff1a;這是最著名的用于創建3D圖形的JavaScript庫之一。它…

中央網信辦部署開展“清朗·整治AI技術濫用”專項行動

為規范AI服務和應用&#xff0c;促進行業健康有序發展&#xff0c;保障公民合法權益&#xff0c;近日&#xff0c;中央網信辦印發通知&#xff0c;在全國范圍內部署開展為期3個月的“清朗整治AI技術濫用”專項行動。 中央網信辦有關負責人表示&#xff0c;本次專項行動分兩個階…

論文閱讀:2024 arxiv Jailbreaking Black Box Large Language Models in Twenty Queries

總目錄 大模型安全相關研究&#xff1a;https://blog.csdn.net/WhiffeYF/article/details/142132328 Jailbreaking Black Box Large Language Models in Twenty Queries https://www.doubao.com/chat/4008882391220226 https://arxiv.org/pdf/2310.08419 速覽 這篇論文是來…

零基礎學指針2

零基礎學指針---大端和小端 零基礎學指針---什么是指針 零基礎學指針---取值運算符*和地址運算符& 零基礎學指針---結構體大小 零基礎學指針5---數據類型轉換 零基礎學指針6---指針數組和數組指針 零基礎學指針7---指針函數和函數指針 零基礎學指針8---函數指針數組…