4

我想制作一个 searchable ComboBox,搜索框是在下拉展开时TextBox出现在上方的a 。我想我需要制作一个自定义控件来实现搜索功能,但首先我只是想让. 这是我当前的尝试,当我尝试展开下拉列表时会产生异常:ItemsPanelComboBoxTextBoxComboBox

<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>

这产生的异常是:

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

我很确定有办法让它做我想做的事,但经过几个小时的谷歌搜索和反复试验,我现在头晕目眩。任何帮助将不胜感激!

4

4 回答 4

6

虽然我对此的回答都是正确的,但我认为他们中的任何一个都没有真正回答我的问题,足以被标记为解决方案。我最终做的是从 ComboBox 派生一个自定义控件(右键单击设计器中的 ComboBox 并选择 Edit Template -> Edit a Copy,这会为我复制的 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>

我将 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);
    }
于 2013-02-28T17:29:27.893 回答
1

您可以使用msdn中的“ComboBox ControlTemplate” 。

您必须 Popup从此样式编辑。我在那里添加Grid了两行控件。在我添加的第一行TextBox中,我认为WatermarkTextBox扩展 WPF 工具包 ( WatermarkTextBox ) 将提供更好的选择。在第二行,你有ScrollViewer你的物品。

TextBox第一行总是可见的,当ComboBox有很多项目时,只有这些元素会在滚动内容中。

下面是完整的例子:

<!-- Fill Brushes -->

<LinearGradientBrush x:Key="NormalBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientBrush.GradientStops>
        <GradientStopCollection>
            <GradientStop Color="#FFF" Offset="0.0"/>
            <GradientStop Color="#CCC" Offset="1.0"/>
        </GradientStopCollection>
    </GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="HorizontalNormalBrush" StartPoint="0,0" EndPoint="1,0">
    <GradientBrush.GradientStops>
        <GradientStopCollection>
            <GradientStop Color="#FFF" Offset="0.0"/>
            <GradientStop Color="#CCC" Offset="1.0"/>
        </GradientStopCollection>
    </GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="LightBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientBrush.GradientStops>
        <GradientStopCollection>
            <GradientStop Color="#FFF" Offset="0.0"/>
            <GradientStop Color="#EEE" Offset="1.0"/>
        </GradientStopCollection>
    </GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="HorizontalLightBrush" StartPoint="0,0" EndPoint="1,0">
    <GradientBrush.GradientStops>
        <GradientStopCollection>
            <GradientStop Color="#FFF" Offset="0.0"/>
            <GradientStop Color="#EEE" Offset="1.0"/>
        </GradientStopCollection>
    </GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DarkBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientBrush.GradientStops>
        <GradientStopCollection>
            <GradientStop Color="#FFF" Offset="0.0"/>
            <GradientStop Color="#AAA" Offset="1.0"/>
        </GradientStopCollection>
    </GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="PressedBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientBrush.GradientStops>
        <GradientStopCollection>
            <GradientStop Color="#BBB" Offset="0.0"/>
            <GradientStop Color="#EEE" Offset="0.1"/>
            <GradientStop Color="#EEE" Offset="0.9"/>
            <GradientStop Color="#FFF" Offset="1.0"/>
        </GradientStopCollection>
    </GradientBrush.GradientStops>
</LinearGradientBrush>

<SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />

<SolidColorBrush x:Key="DisabledBackgroundBrush" Color="#EEE" />

<SolidColorBrush x:Key="WindowBackgroundBrush" Color="#FFF" />

<SolidColorBrush x:Key="SelectedBackgroundBrush" Color="#DDD" />

<!-- Border Brushes -->

<LinearGradientBrush x:Key="NormalBorderBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientBrush.GradientStops>
        <GradientStopCollection>
            <GradientStop Color="#CCC" Offset="0.0"/>
            <GradientStop Color="#444" Offset="1.0"/>
        </GradientStopCollection>
    </GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="HorizontalNormalBorderBrush" StartPoint="0,0" EndPoint="1,0">
    <GradientBrush.GradientStops>
        <GradientStopCollection>
            <GradientStop Color="#CCC" Offset="0.0"/>
            <GradientStop Color="#444" Offset="1.0"/>
        </GradientStopCollection>
    </GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="DefaultedBorderBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientBrush.GradientStops>
        <GradientStopCollection>
            <GradientStop Color="#777" Offset="0.0"/>
            <GradientStop Color="#000" Offset="1.0"/>
        </GradientStopCollection>
    </GradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="PressedBorderBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientBrush.GradientStops>
        <GradientStopCollection>
            <GradientStop Color="#444" Offset="0.0"/>
            <GradientStop Color="#888" Offset="1.0"/>
        </GradientStopCollection>
    </GradientBrush.GradientStops>
</LinearGradientBrush>

<SolidColorBrush x:Key="DisabledBorderBrush" Color="#AAA" />

<SolidColorBrush x:Key="SolidBorderBrush" Color="#888" />

<SolidColorBrush x:Key="LightBorderBrush" Color="#AAA" />

<!-- Miscellaneous Brushes -->
<SolidColorBrush x:Key="GlyphBrush" Color="#444" />

<SolidColorBrush x:Key="LightColorBrush" Color="#DDD" />

<ControlTemplate x:Key="ComboBoxToggleButton" TargetType="ToggleButton">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="20" />
        </Grid.ColumnDefinitions>
        <Border
            x:Name="Border" 
            Grid.ColumnSpan="2"
            CornerRadius="2"
            Background="{StaticResource NormalBrush}"
            BorderBrush="{StaticResource NormalBorderBrush}"
            BorderThickness="1" />
        <Border 
            Grid.Column="0"
            CornerRadius="2,0,0,2" 
            Margin="1" 
            Background="{StaticResource WindowBackgroundBrush}" 
            BorderBrush="{StaticResource NormalBorderBrush}"
            BorderThickness="0,0,1,0" />
        <Path 
            x:Name="Arrow"
            Grid.Column="1"     
            Fill="{StaticResource GlyphBrush}"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Data="M 0 0 L 4 4 L 8 0 Z"/>
    </Grid>
    <ControlTemplate.Triggers>
        <Trigger Property="ToggleButton.IsMouseOver" Value="true">
            <Setter TargetName="Border" Property="Background" Value="{StaticResource DarkBrush}" />
        </Trigger>
        <Trigger Property="ToggleButton.IsChecked" Value="true">
            <Setter TargetName="Border" Property="Background" Value="{StaticResource PressedBrush}" />
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter TargetName="Border" Property="Background" Value="{StaticResource DisabledBackgroundBrush}" />
            <Setter TargetName="Border" Property="BorderBrush" Value="{StaticResource DisabledBorderBrush}" />
            <Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
            <Setter TargetName="Arrow" Property="Fill" Value="{StaticResource DisabledForegroundBrush}" />
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

<ControlTemplate x:Key="ComboBoxTextBox" TargetType="TextBox">
    <Border x:Name="PART_ContentHost" Focusable="False" Background="{TemplateBinding Background}" />
</ControlTemplate>

<Style x:Key="{x:Type ComboBox}" TargetType="ComboBox">
    <Setter Property="SnapsToDevicePixels" Value="true"/>
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
    <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
    <Setter Property="MinWidth" Value="120"/>
    <Setter Property="MinHeight" Value="20"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ComboBox">
                <Grid>
                    <ToggleButton 
                        Name="ToggleButton" 
                        Template="{StaticResource ComboBoxToggleButton}" 
                        Grid.Column="2" 
                        Focusable="false"
                        IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
                        ClickMode="Press">
                    </ToggleButton>
                    <ContentPresenter
                        Name="ContentSite"
                        IsHitTestVisible="False" 
                        Content="{TemplateBinding SelectionBoxItem}"
                        ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
                        ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
                        Margin="3,3,23,3"
                        VerticalAlignment="Center"
                        HorizontalAlignment="Left" />
                    <TextBox x:Name="PART_EditableTextBox"
                             Style="{x:Null}" 
                             Template="{StaticResource ComboBoxTextBox}" 
                             HorizontalAlignment="Left" 
                             VerticalAlignment="Center" 
                             Margin="3,3,23,3"
                             Focusable="True" 
                             Background="Transparent"
                             Visibility="Hidden"
                             IsReadOnly="{TemplateBinding IsReadOnly}"/>
                    <Popup 
                        Name="Popup"
                        Placement="Bottom"
                        IsOpen="{TemplateBinding IsDropDownOpen}"
                        AllowsTransparency="True" 
                        Focusable="False"
                        PopupAnimation="Slide">
                        <Grid 
                            Name="DropDown"
                            SnapsToDevicePixels="True"                
                            MinWidth="{TemplateBinding ActualWidth}"
                            MaxHeight="{TemplateBinding MaxDropDownHeight}">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="30" />
                                <RowDefinition Height="{TemplateBinding MaxDropDownHeight}" />
                            </Grid.RowDefinitions>

                            <Border 
                                x:Name="DropDownBorder"
                                Background="{StaticResource WindowBackgroundBrush}"
                                BorderThickness="1"
                                BorderBrush="{StaticResource SolidBorderBrush}"
                                Grid.RowSpan="2"/>

                            <TextBox Margin="2" VerticalAlignment="Center" />

                            <ScrollViewer Grid.Row="1" Margin="4,6,4,6" SnapsToDevicePixels="True">
                                <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" />
                            </ScrollViewer>
                        </Grid>
                    </Popup>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasItems" Value="false">
                        <Setter TargetName="DropDownBorder" Property="MinHeight" Value="95"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
                    </Trigger>
                    <Trigger Property="IsGrouping" Value="true">
                        <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                    </Trigger>
                    <Trigger SourceName="Popup" Property="Popup.AllowsTransparency" Value="true">
                        <Setter TargetName="DropDownBorder" Property="CornerRadius" Value="4"/>
                        <Setter TargetName="DropDownBorder" Property="Margin" Value="0,2,0,0"/>
                    </Trigger>
                    <Trigger Property="IsEditable"
                             Value="true">
                        <Setter Property="IsTabStop" Value="false"/>
                        <Setter TargetName="PART_EditableTextBox" Property="Visibility"    Value="Visible"/>
                        <Setter TargetName="ContentSite" Property="Visibility" Value="Hidden"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
    </Style.Triggers>
</Style>
于 2013-02-26T19:12:47.513 回答
0

我想你会有几个问题。

  1. 如果您依靠一种样式来显示文本框,那么您将把该文本框的逻辑放在哪里?您需要扩展组合框以添加该逻辑或进行某种复合控件。

  2. 第二个问题是组合框在失去焦点时会关闭。所以,你必须编写一些逻辑来改变这种行为。

这不是不可能的,我只是认为你需要做的不仅仅是创造一种风格。

于 2013-02-26T22:54:01.603 回答
0

是的,它有很多代码,但这是您应该开始考虑使用资源字典的时候。我确实同意其他发帖者的观点,即这种控制可能会让您头疼,因为如果用户不小心并失去焦点,组合框会收缩。

  <Style x:Key="MyCombo" TargetType="{x:Type ComboBox}">
        <Setter Property="Template">
            <Setter.Value>
            <ControlTemplate TargetType="{x:Type ComboBox}">
                <Grid x:Name="MainGrid" 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}}" Margin="1" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
                        <Microsoft_Windows_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">
                                        <StackPanel>
                                            <TextBox>blah blah</TextBox>


                                            <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}"/>
                                        </StackPanel>
                                    </Grid>
                                </ScrollViewer>
                            </Border>
                        </Microsoft_Windows_Themes:SystemDropShadowChrome>
                    </Popup>
                    <ToggleButton BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" Grid.ColumnSpan="2" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ComboBoxReadonlyToggleButton}"/>
                    <ContentPresenter ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" Content="{TemplateBinding SelectionBoxItem}" ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" IsHitTestVisible="false" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
                        <Setter Property="Margin" TargetName="Shdw" Value="0,0,5,5"/>
                        <Setter Property="Color" TargetName="Shdw" Value="#71000000"/>
                    </Trigger>
                    <Trigger Property="HasItems" Value="false">
                        <Setter Property="Height" TargetName="DropDownBorder" Value="95"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                        <Setter Property="Background" Value="#FFF4F4F4"/>
                    </Trigger>
                    <Trigger Property="IsGrouping" Value="true">
                        <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                    </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>
    </Style>
于 2013-02-26T19:21:15.547 回答