在ComboBox的ItemsPanel上方添加其他控件

Stu*_*gan 3 wpf combobox textbox itemspaneltemplate

我想进行搜索ComboBox,搜索框TextBox显示ItemsPanelComboBox下拉列表展开的上方.我想我需要制作一个自定义控件才能实现搜索功能,但首先我只想尝试TextBox使用普通模式进行显示ComboBox.这是我当前的尝试,当我尝试扩展下拉列表时产生异常:

<Style x:Key="FilteredComboBox" TargetType="ComboBox">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <StackPanel>
                    <TextBox/>
                    <StackPanel IsItemsHost="True"
                                Orientation="Horizontal"
                                VerticalAlignment="Center"
                                HorizontalAlignment="Center"/>
                    </StackPanel>

            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
</Style>
Run Code Online (Sandbox Code Playgroud)

这产生的例外是:

无法显式修改用作ItemsControl的ItemsPanel的Panel的Children集合.ItemsControl为Panel生成子元素.

我很确定有办法让这样做我想要的,但经过几个小时的谷歌搜索和反复试验,我的脑袋现在正在旋转.任何帮助将不胜感激!

Stu*_*gan 5

虽然我得到的答案都在正确的轨道上,但我认为他们中的任何一个都没有真正回答我的问题,不足以被标记为解决方案.我最终做的是从ComboBox派生自定义控件(右键单击设计器中的ComboBox并选择编辑模板 - >编辑副本,这会为ComboBox模板生成一大堆XAML代码,我将其复制到我制作的自定义控件项目的Generic.xaml文件.然后我编辑了生成的XAML代码的Popup部分,在ItemsPresenter上面添加了WatermarkTextBox(来自Extended WPF Toolkit),如下所示:

<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
    <Themes:SystemDropShadowChrome x:Name="Shdw" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=MainGrid}">
    <Border x:Name="DropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
        <ScrollViewer x:Name="DropDownScrollViewer">
        <Grid RenderOptions.ClearTypeHint="Enabled">
            <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>
            <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <xtk:WatermarkTextBox Grid.Row="0"
                          Visibility="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource booleanToVisibilityConverter}}"
                          Watermark="Type here to filter..."
                          Text="{Binding SearchFilter, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}">
            </xtk:WatermarkTextBox>
            <ItemsPresenter x:Name="ItemsPresenter" Grid.Row="1" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            </Grid>
        </Grid>
        </ScrollViewer>
    </Border>
    </Themes:SystemDropShadowChrome>
</Popup>
Run Code Online (Sandbox Code Playgroud)

我正在绑定Text的SearchFilter属性是我的自定义控件的代码隐藏中的属性,然后执行ComboBox中值的实际过滤.这有点超出了原始问题的范围,但是如果有人好奇的话,这就是我在做过滤的方式:

public class SearchableComboBox : ComboBox
{
    public const string SearchFilterPropertyName = "SearchFilter";
    public readonly static DependencyProperty SearchFilterProperty;
    public string SearchFilter
    {
        get { return (string)GetValue(SearchFilterProperty); }
        set { SetValue(SearchFilterProperty, value); }
    }

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

        SearchFilterProperty = DependencyProperty.Register(SearchFilterPropertyName, typeof(string), typeof(SearchableComboBox),
            new PropertyMetadata(string.Empty, new PropertyChangedCallback(SearchFilter_PropertyChanged)));

    }

    private static void SearchFilter_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((SearchableComboBox)d).RefreshFilter();
    }

    private void RefreshFilter()
    {
        if (this.ItemsSource != null)
        {
            ICollectionView view = CollectionViewSource.GetDefaultView(this.ItemsSource);
            view.Refresh();
        }
    }

    private bool FilterPredicate(object value)
    {
        if (value == null)
            return false;

        if (string.IsNullOrEmpty(SearchFilter))
            return true;

        return value.ToString().Contains(SearchFilter, StringComparison.CurrentCultureIgnoreCase);
    }

    protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
    {
        if (newValue != null)
        {
            ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
            view.Filter += this.FilterPredicate;
        }

        if (oldValue != null)
        {
            ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
            view.Filter -= this.FilterPredicate;
        }

        base.OnItemsSourceChanged(oldValue, newValue);
    }
Run Code Online (Sandbox Code Playgroud)