我知道CaretIndex不是依赖项属性。

因此,我将其注册如下:

 public class TextBoxHelper : TextBox
    {
        public static readonly DependencyProperty CaretIndexProperty
                                    = DependencyProperty.Register
                                        (
                                            "CaretIndex",
                                            typeof(int),
                                            typeof(TextBoxHelper),
                                            new FrameworkPropertyMetadata
                                                    (
                                                        0,
                                                        FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                                                        CaretIndexChanged
                                                    )
                                        );

        public static int GetCaretIndex(DependencyObject obj)
        {
            return (int)obj.GetValue(CaretIndexProperty);
        }

        public static void SetCaretIndex(DependencyObject obj, int value)
        {
            obj.SetValue(CaretIndexProperty, value);
        }

        //public new int CaretIndex
        //{
        //    get { return (int)GetValue(CaretIndexProperty); }
        //    set { SetValue(CaretIndexProperty, value); }
        //}

        protected override void OnTextChanged(TextChangedEventArgs e)
        {
            base.OnTextChanged(e);
            CaretIndex = base.CaretIndex;
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);
            CaretIndex = base.CaretIndex;
        }

        protected override void OnKeyUp(KeyEventArgs e)
        {
            base.OnKeyUp(e);
            CaretIndex = base.CaretIndex;
        }

        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            base.OnMouseDown(e);
            CaretIndex = base.CaretIndex;
        }

        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            base.OnMouseUp(e);
            CaretIndex = base.CaretIndex;
        }

        private static void CaretIndexChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (obj is TextBox)
            {
                ((TextBox)obj).CaretIndex = (int)e.NewValue;
            }
        }
    }


然后,我在ViewModel中创建了一个名为CaretIndex的属性。它实现了INotifyPropertyChanged接口。

private int _caretIndex;
public int CaretIndex
{
    get { return _caretIndex; }
    set
    {
        _caretIndex = value;
        OnPropertyChanged("CaretIndex");
    }
}


然后,我在ComboBox中创建绑定,如下所示:

<ComboBox x:Name="cbUnder" ItemsSource="{Binding GroupsAndCorrespondingEffects}"
    IsEditable="True" SelectedItem="{Binding SelectedGroup, Mode=TwoWay}"
    Text="{Binding InputValue, UpdateSourceTrigger=PropertyChanged}" TextSearch.TextPath="GroupName"
    Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="3"
    vm:TextBoxHelper.CaretIndex="{Binding CaretIndex, UpdateSourceTrigger=PropertyChanged}">
    <ComboBox.Resources>
        <DataTemplate DataType="{x:Type vm:GroupAndCorrespondingEffect}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding GroupName}" Width="250">
                    <TextBlock.Style>
                        <Style TargetType="TextBlock">
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding IsHighlighted}" Value="True">
                                    <Setter Property="Foreground" Value="Blue" />
                                    <Setter Property="FontWeight" Value="Bold"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </TextBlock.Style>
                </TextBlock>
                <TextBlock Text="{Binding CorrespondingEffect}" />
            </StackPanel>
        </DataTemplate>
    </ComboBox.Resources>
</ComboBox>


当CaretIndex更改时,我仍然没有收到通知。

从上面的代码中,我得到了IsEditable = TrueIsTextSearchEnabled = True

但是,当我在组合框中键入任何字符时,它将在组合框的文本框中附加整个项目的名称。

我实际上希望有一个组合框来突出显示与我键入的内容匹配的所有项目,但是由于附加了文本,我只能使一个项目突出显示。

因此,我需要输入的文字。即未选中的文本。 (因为还选择了附加的文本)。

因此,如果获得caretIndex,则可以在文本上使用substring方法获取输入的内容。据此它将突出显示文本。

更新资料

<Style TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ComboBox">
                <TextBox x:Name="PART_EditableTextBox" CaretIndex="vm:TextBoxHelper.CaretIndex" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>


我已经尝试了上面的代码,但是它说我输入的字符串格式不正确。

因此,我将上面的代码替换为:

<Style TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ComboBox">
                <TextBox x:Name="PART_EditableTextBox" vm:TextBoxHelper.CaretIndex="{Binding CaretIndex, UpdateSourceTrigger=PropertyChanged}" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>


现在我没有收到任何错误,但是我的组合框看起来像一个文本框。我的意思是它已经失去了下拉菜单部分。我已经在视图模型中使用断点进行了检查。但是当caretIndex更改时,我仍然没有收到通知。

更新2

<TextBox x:Name="PART_EditableTextBox" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsReadOnly="{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}" Margin="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
                <TextBox.Style>
                    <Style TargetType="{x:Type TextBox}">
                        <Setter Property="vm:TextBoxHelper.CaretIndex" Value="{Binding CaretIndex, UpdateSourceTrigger=PropertyChanged}" />
            .
.
.


上面的代码不起作用,所以我如下更新了它:

<vm:TextBoxHelper x:Name="PART_EditableTextBox" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsReadOnly="{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}" Margin="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
                <vm:TextBoxHelper.Style>
                    <Style TargetType="{x:Type vm:TextBoxHelper}">
                        <Setter Property="vm:TextBoxHelper.CaretIndex" Value="{Binding CaretIndex, UpdateSourceTrigger=PropertyChanged}" />
            .
.
.


仍然不起作用。

更新3

这是我的App.xaml文件

<Application
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:comboFDD="clr-namespace:ERP_Lite_Trial.Views.DesignRelatedCode"
    x:Class="ERP_Lite_Trial.App" StartupUri="Views/MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Views/Pages/ResourceDictionary1.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <Style TargetType="{x:Type ComboBox}">
                <Setter Property="comboFDD:ComboBox_ForceDropDown.OpenDropDownAutomatically" Value="True"/>
            </Style>
        </ResourceDictionary>
    </Application.Resources>
</Application>


更新4

我的ViewModel中也有一个属性InputValue

private string _inputValue;
public string InputValue
{
    get { return _inputValue; }
    set
    {
        _inputValue = value;
        OnPropertyChanged("GroupsAndCorrespondingEffects");

        for (int i = 0; i < GroupsAndCorrespondingEffects.Count; i++)
        {

            string WordToSearch = _inputValue;

            if (_caretIndex != 0 || _caretIndex != null)
            {
                WordToSearch = _inputValue.Substring(0, _caretIndex);
            }

            GroupsAndCorrespondingEffects[i].IsHighlighted = GroupsAndCorrespondingEffects[i].GroupName.StartsWith(WordToSearch);
        }
    }
}


我拥有的另一个属性是IsHilighted。此属性在GroupsAndCorrespondingEffects类中定义。使用此类,我可以从数据库中获取数据。

GroupsAndCorrespondingEffects.cs的代码

public class GroupAndCorrespondingEffect : INotifyPropertyChanged
{
    private string _groupName;
    public string GroupName
    {
        get
        {
            return _groupName;
        }
        set
        {
            _groupName = value;
            OnPropertyChanged("GroupName");
        }
    }

    private string _correspondingEffect;
    public string CorrespondingEffect
    {
        get
        {
            return _correspondingEffect;
        }
        set
        {
            _correspondingEffect = value;
            OnPropertyChanged("CorrespondingEffect");
        }
    }

    private bool _isHighlighted;
    public bool IsHighlighted
    {
        get
        {
            return _isHighlighted;
        }
        set
        {
            _isHighlighted = value;
            OnPropertyChanged("IsHighlighted");
        }
    }

    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}


ViewModel中调用属性的过程如下:

现在,当我在组合框中键入1st Character时:

CaretIndex = 0

CaretIndex = 0

InputValue = Text of combobox

CaretIndex = 1

CaretIndex = 1


当我在组合框中键入第二个字符时:

CaretIndex = 1

InputValue = Text of combobox

CaretIndex = 2


当我在组合框中键入第三个字符时:

CaretIndex = 2

InputValue = Text of combobox

CaretIndex = 3


等等....

我在组合框中突出显示项目的逻辑是基于CaretIndex的更改。此逻辑写在InputValue属性的set部分中。但是由于上述事实,在CaretIndex获取新值之前调用了InputValue,所以我得到了不正确的HighLights。

更新5-保留选择丢失

我在TextBoxHelper类中添加了SelectionStart和SelectionLength属性,因为它们不是依赖项属性。

这是代码:

    public static readonly DependencyProperty BindableSelectionStartProperty
                    = DependencyProperty.RegisterAttached
                        (
                            "BindableSelectionStart",
                            typeof(int),
                            typeof(TextBoxHelper),
                            new PropertyMetadata
                                    (
                                        BindableSelectionStartChanged
                                    )
                        );

    public static readonly DependencyProperty BindableSelectionLengthProperty
                    = DependencyProperty.RegisterAttached
                        (
                            "BindableSelectionLength",
                            typeof(int),
                            typeof(TextBoxHelper),
                            new PropertyMetadata
                                    (
                                        BindableSelectionLengthChanged
                                    )
                        );

    public static int GetBindableSelectionStart(DependencyObject obj)
    {
        return (int)obj.GetValue(BindableSelectionStartProperty);
    }

    public static void SetBindableSelectionStart(DependencyObject obj, int value)
    {
        obj.SetValue(BindableSelectionStartProperty, value);
    }

    public int BindableSelectionStart
    {
        get
        {
            return (int)this.GetValue(BindableSelectionStartProperty);
        }
        set
        {
            this.SetValue(BindableSelectionStartProperty, value);
        }
    }

    public static int GetBindableSelectionLength(DependencyObject obj)
    {
        return (int)obj.GetValue(BindableSelectionLengthProperty);
    }

    public static void SetBindableSelectionLength(DependencyObject obj, int value)
    {
        obj.SetValue(BindableSelectionLengthProperty, value);
    }

    public int BindableSelectionLength
    {
        get
        {
            return (int)this.GetValue(BindableSelectionLengthProperty);
        }
        set
        {
            this.SetValue(BindableSelectionLengthProperty, value);
        }
    }

    private static void BindableSelectionStartChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is TextBox)
        {
            ((TextBox)d).SelectionStart = (int)e.NewValue;
        }
    }

    private static void BindableSelectionLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is TextBox)
        {
            ((TextBox)d).SelectionLength = (int)e.NewValue;
        }
    }

    protected override void OnSelectionChanged(RoutedEventArgs e)
    {
        base.OnSelectionChanged(e);
        BindableSelectionStart = base.SelectionStart;
        BindableSelectionLength = base.SelectionLength;
    }


然后,我在ViewModel中创建了相应的属性。

    private int _selectionStart;
    public int SelectionStart
    {
        get
        {
            return _selectionStart;
        }
        set
        {
            _selectionStart = value;
            OnPropertyChanged("SelectionStart");
        }
    }

    private int _selectionLength;
    public int SelectionLength
    {
        get
        {
            return _selectionLength;
        }
        set
        {
            _selectionLength = value;
            OnPropertyChanged("SelectionLength");
        }
    }


之后,我将ViewModel的CaretIndexInputValue属性更改如下:

    private int _caretIndex;
    public int CaretIndex
    {
        get { return _caretIndex; }
        set
        {
            _caretIndex = value;
            OnPropertyChanged("CaretIndex");

            if (InputValue != null && CaretIndex >= 0)
            {
                SelectionStart = CaretIndex;
                Debug.WriteLine(string.Format("Selection Start : {0}", SelectionStart));
                SelectionLength = InputValue.Length - CaretIndex;
                Debug.WriteLine(string.Format("Selection Length : {0}", SelectionLength));
            }

            for (int i = 0; i < GroupsAndCorrespondingEffects.Count; i++)
            {

                string WordToSearch = InputValue;

                if (_caretIndex != 0 && _caretIndex > 0)
                {
                    WordToSearch = InputValue.Substring(0, _caretIndex);
                }

                if (WordToSearch != null)
                {
                    GroupsAndCorrespondingEffects[i].IsHighlighted = GroupsAndCorrespondingEffects[i].GroupName.StartsWith(WordToSearch);
                }
            }
        }
    }

    private string _inputValue;
    public string InputValue
    {
        get { return _inputValue; }
        set
        {
            _inputValue = value;
            OnPropertyChanged("GroupsAndCorrespondingEffects");

            if (InputValue != null && CaretIndex >= 0)
            {
                SelectionStart = CaretIndex;
                Debug.WriteLine(string.Format("Selection Start : {0}", SelectionStart));
                SelectionLength = InputValue.Length - CaretIndex;
                Debug.WriteLine(string.Format("Selection Length : {0}", SelectionLength));
            }

            for (int i = 0; i < GroupsAndCorrespondingEffects.Count; i++)
            {

                string WordToSearch = _inputValue;

                if (_caretIndex != 0 && _caretIndex > 0 && _caretIndex < _inputValue.Length)
                {
                    WordToSearch = _inputValue.Substring(0, _caretIndex);
                }

                GroupsAndCorrespondingEffects[i].IsHighlighted = GroupsAndCorrespondingEffects[i].GroupName.StartsWith(WordToSearch);

            }
        }
    }


我所做的最后更改是在ResourceDictionary中,如下所示:

<vm:TextBoxHelper x:Name="PART_EditableTextBox" MyCaretIndex="{Binding CaretIndex}"
                                                BindableSelectionStart="{Binding SelectionStart}"
                                                BindableSelectionLength="{Binding SelectionLength}"
...........


在输出窗口中,我可以看到SelectionStart和SelectionLength发生了变化,但是在组合框中看不到任何视觉变化。

最佳答案

您的代码有几个问题:

1)通过在CaretIndex = base.CaretIndex中执行TextBoxHelper,实际上是在设置相同的属性,该属性不会触发或更改任何内容。

2)您正在设置ComboBox默认模板,但是要设置的是IsEditable为true时的ComboBox模板。否则,WPF将使用您提供的模板作为默认模板,并且将IsEditable设置为true时,WPF就会引入默认的Editable模板,该模板使用TextBox,而不是用户TextBoxHelper。

因此,这就是我为使其工作而要做的事情:

1)更新了组合框样式以仅在IsEditableTrue时设置模板

<Style TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
    <Style.Triggers>
        <Trigger Property="IsEditable" Value="True">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ComboBox">
                        <local:TextBoxHelper x:Name="PART_EditableTextBox" MyCaretIndex="{Binding CaretIndex}" />
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Trigger>
    </Style.Triggers>
</Style>


2)将CaretIndexTextBoxHelper属性的名称更改为MyCaretIndex,这是代码:

public sealed class TextBoxHelper: TextBox
{
    public static readonly DependencyProperty MyCaretIndexProperty
                                = DependencyProperty.Register
                                    (
                                        "MyCaretIndex",
                                        typeof(int),
                                        typeof(TextBoxHelper),
                                        new FrameworkPropertyMetadata
                                                (
                                                    0,
                                                    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                                                    MyCaretIndexChanged
                                                )
                                    );

    public static int GetMyCaretIndex(DependencyObject obj)
    {
        return (int)obj.GetValue(MyCaretIndexProperty);
    }

    public static void SetMyCaretIndex(DependencyObject obj, int value)
    {
        obj.SetValue(MyCaretIndexProperty, value);
    }

    public int MyCaretIndex
    {
        get { return (int)GetValue(MyCaretIndexProperty); }
        set { SetValue(MyCaretIndexProperty, value); }
    }

    protected override void OnTextChanged(TextChangedEventArgs e)
    {
        base.OnTextChanged(e);
        MyCaretIndex = base.CaretIndex;
    }

    protected override void OnKeyDown(KeyEventArgs e)
    {
        base.OnKeyDown(e);
        MyCaretIndex = base.CaretIndex;
    }

    protected override void OnKeyUp(KeyEventArgs e)
    {
        base.OnKeyUp(e);
        MyCaretIndex = base.CaretIndex;
    }

    protected override void OnMouseDown(MouseButtonEventArgs e)
    {
        base.OnMouseDown(e);
        MyCaretIndex = base.CaretIndex;
    }

    protected override void OnMouseUp(MouseButtonEventArgs e)
    {
        base.OnMouseUp(e);
        MyCaretIndex = base.CaretIndex;
    }

    private static void MyCaretIndexChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (obj is TextBox)
        {
            ((TextBox)obj).CaretIndex = (int)e.NewValue;
        }
    }
}


和我的CaretIndex属性的ViewModel代码供您参考:

public int CaretIndex
{
    get { return _caretIndex; }
    set
    {
        _caretIndex = value;
        Trace.WriteLine(String.Format("Caret Index {0}", _caretIndex));
        if(PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs("CaretIndex"));
    }
}


查看代码:

<ComboBox ItemsSource="{Binding Items}" IsEditable="True" />


经过以上更改,我可以在视图模型中看到正确的CaretIndex值。

请注意,ComboBox在该可编辑模板上看起来并不漂亮(它不会具有切换按钮来下拉包含所有值的弹出窗口)。但是,我想这不在此问题的范围内,但是,请让我知道您是否也想这样做。

更新

我已经使用Blend提取了ComboBox的默认模板,只是将TextBox替换为TextBoxHelper。我删除了几个阴影边框,因为它们是指PresentationFramework.Aero2.dll。

这是完整的模板以及模板引用的所有资源,非常冗长:

<LinearGradientBrush x:Key="ComboBox.Static.Background" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FFF0F0F0" Offset="0.0"/>
    <GradientStop Color="#FFE5E5E5" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.Static.Border" Color="#FFACACAC"/>
<SolidColorBrush x:Key="ComboBox.Static.Editable.Background" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="ComboBox.Static.Editable.Border" Color="#FFABADB3"/>
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Background" Color="Transparent"/>
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Border" Color="Transparent"/>
<SolidColorBrush x:Key="ComboBox.MouseOver.Glyph" Color="#FF000000"/>
<LinearGradientBrush x:Key="ComboBox.MouseOver.Background" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FFECF4FC" Offset="0.0"/>
    <GradientStop Color="#FFDCECFC" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.MouseOver.Border" Color="#FF7EB4EA"/>
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Background" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Border" Color="#FF7EB4EA"/>
<LinearGradientBrush x:Key="ComboBox.MouseOver.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FFEBF4FC" Offset="0.0"/>
    <GradientStop Color="#FFDCECFC" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Button.Border" Color="#FF7EB4EA"/>
<SolidColorBrush x:Key="ComboBox.Pressed.Glyph" Color="#FF000000"/>
<LinearGradientBrush x:Key="ComboBox.Pressed.Background" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FFDAECFC" Offset="0.0"/>
    <GradientStop Color="#FFC4E0FC" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.Pressed.Border" Color="#FF569DE5"/>
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Background" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Border" Color="#FF569DE5"/>
<LinearGradientBrush x:Key="ComboBox.Pressed.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#FFDAEBFC" Offset="0.0"/>
    <GradientStop Color="#FFC4E0FC" Offset="1.0"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Button.Border" Color="#FF569DE5"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Glyph" Color="#FFBFBFBF"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Background" Color="#FFF0F0F0"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Border" Color="#FFD9D9D9"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Background" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Border" Color="#FFBFBFBF"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Background" Color="Transparent"/>
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Border" Color="Transparent"/>
<SolidColorBrush x:Key="ComboBox.Static.Glyph" Color="#FF606060"/>
<Style x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}">
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="IsTabStop" Value="false"/>
    <Setter Property="Focusable" Value="false"/>
    <Setter Property="ClickMode" Value="Press"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ToggleButton}">
                <Border x:Name="templateRoot" BorderBrush="{StaticResource ComboBox.Static.Border}" BorderThickness="{TemplateBinding BorderThickness}" Background="{StaticResource ComboBox.Static.Background}" SnapsToDevicePixels="true">
                    <Border x:Name="splitBorder" BorderBrush="Transparent" BorderThickness="1" HorizontalAlignment="Right" Margin="0" SnapsToDevicePixels="true" Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}">
                        <Path x:Name="arrow" Data="F1 M 0,0 L 2.667,2.66665 L 5.3334,0 L 5.3334,-1.78168 L 2.6667,0.88501 L0,-1.78168 L0,0 Z" Fill="{StaticResource ComboBox.Static.Glyph}" HorizontalAlignment="Center" Margin="0" VerticalAlignment="Center"/>
                    </Border>
                </Border>
                <ControlTemplate.Triggers>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="true"/>
                            <Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="false"/>
                            <Condition Binding="{Binding IsPressed, RelativeSource={RelativeSource Self}}" Value="false"/>
                            <Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="true"/>
                        </MultiDataTrigger.Conditions>
                        <Setter Property="Background" TargetName="templateRoot" Value="{StaticResource ComboBox.Static.Editable.Background}"/>
                        <Setter Property="BorderBrush" TargetName="templateRoot" Value="{StaticResource ComboBox.Static.Editable.Border}"/>
                        <Setter Property="Background" TargetName="splitBorder" Value="{StaticResource ComboBox.Static.Editable.Button.Background}"/>
                        <Setter Property="BorderBrush" TargetName="splitBorder" Value="{StaticResource ComboBox.Static.Editable.Button.Border}"/>
                    </MultiDataTrigger>
                    <Trigger Property="IsMouseOver" Value="true">
                        <Setter Property="Fill" TargetName="arrow" Value="{StaticResource ComboBox.MouseOver.Glyph}"/>
                    </Trigger>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
                            <Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="false"/>
                        </MultiDataTrigger.Conditions>
                        <Setter Property="Background" TargetName="templateRoot" Value="{StaticResource ComboBox.MouseOver.Background}"/>
                        <Setter Property="BorderBrush" TargetName="templateRoot" Value="{StaticResource ComboBox.MouseOver.Border}"/>
                    </MultiDataTrigger>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
                            <Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="true"/>
                        </MultiDataTrigger.Conditions>
                        <Setter Property="Background" TargetName="templateRoot" Value="{StaticResource ComboBox.MouseOver.Editable.Background}"/>
                        <Setter Property="BorderBrush" TargetName="templateRoot" Value="{StaticResource ComboBox.MouseOver.Editable.Border}"/>
                        <Setter Property="Background" TargetName="splitBorder" Value="{StaticResource ComboBox.MouseOver.Editable.Button.Background}"/>
                        <Setter Property="BorderBrush" TargetName="splitBorder" Value="{StaticResource ComboBox.MouseOver.Editable.Button.Border}"/>
                    </MultiDataTrigger>
                    <Trigger Property="IsPressed" Value="true">
                        <Setter Property="Fill" TargetName="arrow" Value="{StaticResource ComboBox.Pressed.Glyph}"/>
                    </Trigger>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding IsPressed, RelativeSource={RelativeSource Self}}" Value="true"/>
                            <Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="false"/>
                        </MultiDataTrigger.Conditions>
                        <Setter Property="Background" TargetName="templateRoot" Value="{StaticResource ComboBox.Pressed.Background}"/>
                        <Setter Property="BorderBrush" TargetName="templateRoot" Value="{StaticResource ComboBox.Pressed.Border}"/>
                    </MultiDataTrigger>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding IsPressed, RelativeSource={RelativeSource Self}}" Value="true"/>
                            <Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="true"/>
                        </MultiDataTrigger.Conditions>
                        <Setter Property="Background" TargetName="templateRoot" Value="{StaticResource ComboBox.Pressed.Editable.Background}"/>
                        <Setter Property="BorderBrush" TargetName="templateRoot" Value="{StaticResource ComboBox.Pressed.Editable.Border}"/>
                        <Setter Property="Background" TargetName="splitBorder" Value="{StaticResource ComboBox.Pressed.Editable.Button.Background}"/>
                        <Setter Property="BorderBrush" TargetName="splitBorder" Value="{StaticResource ComboBox.Pressed.Editable.Button.Border}"/>
                    </MultiDataTrigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Fill" TargetName="arrow" Value="{StaticResource ComboBox.Disabled.Glyph}"/>
                    </Trigger>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="false"/>
                            <Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="false"/>
                        </MultiDataTrigger.Conditions>
                        <Setter Property="Background" TargetName="templateRoot" Value="{StaticResource ComboBox.Disabled.Background}"/>
                        <Setter Property="BorderBrush" TargetName="templateRoot" Value="{StaticResource ComboBox.Disabled.Border}"/>
                    </MultiDataTrigger>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="false"/>
                            <Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="true"/>
                        </MultiDataTrigger.Conditions>
                        <Setter Property="Background" TargetName="templateRoot" Value="{StaticResource ComboBox.Disabled.Editable.Background}"/>
                        <Setter Property="BorderBrush" TargetName="templateRoot" Value="{StaticResource ComboBox.Disabled.Editable.Border}"/>
                        <Setter Property="Background" TargetName="splitBorder" Value="{StaticResource ComboBox.Disabled.Editable.Button.Background}"/>
                        <Setter Property="BorderBrush" TargetName="splitBorder" Value="{StaticResource ComboBox.Disabled.Editable.Button.Border}"/>
                    </MultiDataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
    <Style.Triggers>
        <Trigger Property="IsEditable" Value="True">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Grid x:Name="templateRoot" SnapsToDevicePixels="true">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Width="0"/>
                            </Grid.ColumnDefinitions>
                            <Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
                                    <Border x:Name="dropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
                                        <ScrollViewer x:Name="DropDownScrollViewer">
                                            <Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
                                                <Canvas x:Name="canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                                                    <Rectangle x:Name="opaqueRect" Fill="{Binding Background, ElementName=dropDownBorder}" Height="{Binding ActualHeight, ElementName=dropDownBorder}" Width="{Binding ActualWidth, ElementName=dropDownBorder}"/>
                                                </Canvas>
                                                <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                                            </Grid>
                                        </ScrollViewer>
                                    </Border>
                            </Popup>
                            <ToggleButton x:Name="toggleButton" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="2" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ComboBoxToggleButton}"/>
                            <Border x:Name="border" Margin="{TemplateBinding BorderThickness}">
                                <local:TextBoxHelper x:Name="PART_EditableTextBox" MyCaretIndex="{Binding CaretIndex}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsReadOnly="{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}" Margin="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
                            </Border>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Opacity" TargetName="border" Value="0.56"/>
                            </Trigger>
                            <Trigger Property="IsKeyboardFocusWithin" Value="true">
                                <Setter Property="Foreground" Value="Black"/>
                            </Trigger>
                            <Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false">
                                <Setter Property="Canvas.Top" TargetName="opaqueRect" Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}"/>
                                <Setter Property="Canvas.Left" TargetName="opaqueRect" Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Trigger>
    </Style.Triggers>
</Style>


更新2

解决了“选定的文本丢失”问题:

TextBoxHelper中的MyCaretIndexChanged方法更新为:

private static void MyCaretIndexChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    if (obj is TextBox && (int)e.OldValue != (int)e.NewValue)
    {
        var textBox = (TextBox) obj;
        textBox.CaretIndex = (int)e.NewValue;
        if (!string.IsNullOrEmpty(textBox.Text))
            textBox.Select(textBox.CaretIndex, textBox.Text.Length - textBox.CaretIndex);
    }
}

10-08 03:00