UWP 中 ComboBox 的 CompositeCollection 的替代方案?

Alternative of CompositeCollection for ComboBox in UWP?

我想为我的 ComboBox 设置默认文本,例如 "Please Select"。对于 WPF,可以使用 CompositeCollection 来完成,如下所示,但是 CompositeCollection 在 UWP 中不可用,那么有什么替代方法吗?

<ComboBox SelectedIndex="0">
    <ComboBox.ItemsSource>
        <CompositeCollection>
            <ListBoxItem>Please Select</ListBoxItem>
            <CollectionContainer Collection="{Binding Source={StaticResource YOURDATASOURCE}}" />
        </CompositeCollection>
    </ComboBox.ItemsSource>
</ComboBox>

I want to set a default text to my ComboBox, something like this "Please Select".

ComboBox 有 PlaceholderText 属性 用于显示在控件中,直到值被用户操作或其他一些操作更改。

<ComboBox  PlaceholderText="Please Select" Width="200">
<x:String>Blue<x:String>
<x:String>Green<x:String>
<x:String>Red<x:String>
<x:String>Yellow<x:String>
</ComboBox>

更新

如果你需要一个项目成为默认选项,你可以使用SelectedItem or SelectIndex来实现。

在花了几个小时搜索解决方案后,我遇到了 this post,它通过在 ComboBox 在所选项目旁边。提供的解决方案是在 WPF 中,所以如果有人想要这个用于他们的 UWP 项目,请参考下面的代码:

自定义组合框样式:

<Style TargetType="ComboBox" x:Key="ComboBoxWithClearButtonStyle">
        <Setter Property="Padding" Value="12,5,0,7" />
        <Setter Property="MinWidth" Value="{ThemeResource ComboBoxThemeMinWidth}" />
        <Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}" />
        <Setter Property="Background" Value="{ThemeResource SystemControlBackgroundAltMediumLowBrush}" />
        <Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumLowBrush}" />
        <Setter Property="BorderThickness" Value="{ThemeResource ComboBoxBorderThemeThickness}" />
        <Setter Property="TabNavigation" Value="Once" />
        <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
        <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
        <Setter Property="ScrollViewer.HorizontalScrollMode" Value="Disabled" />
        <Setter Property="ScrollViewer.VerticalScrollMode" Value="Auto" />
        <Setter Property="ScrollViewer.IsVerticalRailEnabled" Value="True" />
        <Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False" />
        <Setter Property="ScrollViewer.BringIntoViewOnFocusChange" Value="True" />
        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        <Setter Property="HorizontalAlignment" Value="Left" />
        <Setter Property="VerticalAlignment" Value="Top" />
        <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
        <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <CarouselPanel />
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ComboBox">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal" />
                                <VisualState x:Name="PointerOver">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background" Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlPageBackgroundAltMediumBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background"
                                           Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightBaseMediumBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background"
                                           Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlBackgroundListMediumBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background"
                                           Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightBaseMediumLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background" Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background" Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderContentPresenter" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseMediumLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseMediumLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderTextBlock"
                                           Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseMediumLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="DropDownGlyph"
                                           Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseMediumLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="FocusStates">
                                <VisualState x:Name="Focused">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HighlightBackground"
                               Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightTransparentBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <DoubleAnimation Storyboard.TargetName="HighlightBackground"
                               Storyboard.TargetProperty="Opacity"
                               To="1"
                               Duration="0" />
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                         Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderTextBlock"
                                           Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="DropDownGlyph"
                   Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseMediumHighBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="FocusedPressed" >
                                    <Storyboard>
                                        <DoubleAnimation
                    Storyboard.TargetName="HighlightBackground"
                    Storyboard.TargetProperty="Opacity"
                    To="1"
                    Duration="0" />
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                         Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderTextBlock"
                                           Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="DropDownGlyph"
                   Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseMediumHighBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Unfocused" />
                                <VisualState x:Name="PointerFocused" />
                                <VisualState x:Name="FocusedDropDown">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames
                    Storyboard.TargetName="PopupBorder"
                    Storyboard.TargetProperty="Visibility"
                    Duration="0">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="DropDownStates">
                                <VisualState x:Name="Opened">
                                    <Storyboard>
                                        <SplitOpenThemeAnimation
                    OpenedTargetName="PopupBorder"
                    ClosedTargetName="ContentPresenter"
                    OffsetFromCenter="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.DropDownOffset}"
                    OpenedLength="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.DropDownOpenedHeight}"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Closed">
                                    <Storyboard>
                                        <SplitCloseThemeAnimation
                    OpenedTargetName="PopupBorder"
                    ClosedTargetName="ContentPresenter"
                    OffsetFromCenter="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.DropDownOffset}"
                    OpenedLength="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.DropDownOpenedHeight}"/>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <ContentPresenter x:Name="HeaderContentPresenter"
            x:DeferLoadStrategy="Lazy"
            Margin="{ThemeResource ComboBoxHeaderThemeMargin}"
            FlowDirection="{TemplateBinding FlowDirection}"
            FontWeight="{ThemeResource ComboBoxHeaderThemeFontWeight}"
            Visibility="Collapsed"
            Content="{TemplateBinding Header}"
            ContentTemplate="{TemplateBinding HeaderTemplate}" />
                        <Border x:Name="Background"
            Grid.Row="1"
            Grid.ColumnSpan="2"
            Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}" />
                        <Border x:Name="HighlightBackground"
            Grid.Row="1"
            Grid.ColumnSpan="2"
            Background="{ThemeResource SystemControlHighlightListAccentLowBrush}"
            BorderBrush="{ThemeResource SystemControlHighlightBaseMediumLowBrush}"
            BorderThickness="{TemplateBinding BorderThickness}"
            Opacity="0" />
                        <ContentPresenter x:Name="ContentPresenter"
            Grid.Row="1"
            Margin="{TemplateBinding Padding}"
            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
            VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                            <TextBlock x:Name="PlaceholderTextBlock"
              Text="{TemplateBinding PlaceholderText}"
              Foreground="{ThemeResource SystemControlPageTextBaseHighBrush}"/>
                        </ContentPresenter>
                        <Grid Grid.Row="1"
                                    Grid.Column="1"
                                    Margin="0,10,10,10">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>
                            <Button x:Name="ClearButton"
                                    Foreground="Black"
                                    FontFamily="{ThemeResource SymbolThemeFontFamily}"
                                    FontSize="12"
                                    Content="&#xE894;"
                                    VerticalAlignment="Center"
                                    BorderThickness="0"
                                    Background="Transparent"
                                    Height="20"
                                    Width="30"
                                    Padding="5,0"
                                    Style="{StaticResource NoHoverButtonStyle}"
                                    AutomationProperties.AccessibilityView="Raw" />
                            <FontIcon x:Name="DropDownGlyph"
                                      Grid.Column="1"
            IsHitTestVisible="False"
            Foreground="{ThemeResource SystemControlForegroundBaseMediumHighBrush}"
            FontFamily="{ThemeResource SymbolThemeFontFamily}"
            FontSize="12"
            Glyph="&#xE0E5;"
            HorizontalAlignment="Right"
            VerticalAlignment="Center"
            AutomationProperties.AccessibilityView="Raw" />
                        </Grid>
                        <Popup x:Name="Popup">
                            <Border
              x:Name="PopupBorder"
              Background="{ThemeResource SystemControlBackgroundChromeMediumLowBrush}"
              BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
              BorderThickness="{ThemeResource ComboBoxDropdownBorderThickness}"
              Margin="0,-1,0,-1"
              HorizontalAlignment="Stretch">
                                <ScrollViewer x:Name="ScrollViewer"
                Foreground="{ThemeResource SystemControlForegroundBaseHighBrush}"
                MinWidth="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.DropDownContentMinWidth}"
                VerticalSnapPointsType="OptionalSingle"
                VerticalSnapPointsAlignment="Near"
                HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}"
                HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
                VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}"
                VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
                IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}"
                IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}"
                IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
                BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}"
                ZoomMode="Disabled"
                AutomationProperties.AccessibilityView="Raw">
                                    <ItemsPresenter Margin="{ThemeResource ComboBoxDropdownContentMargin}" />
                                </ScrollViewer>
                            </Border>
                        </Popup>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

XAML ComboBox 的行为:

public sealed class ComboBoxBehavior : DependencyObject, IBehavior
    {
        public DependencyObject AssociatedObject { get; set; }

        public bool IsNullable
        {
            get => (bool)this.GetValue(IsNullableProperty);
            set => this.SetValue(IsNullableProperty, value);
        }

        public static readonly DependencyProperty IsNullableProperty = DependencyProperty.Register(
            "IsNullable",
            typeof(bool),
            typeof(ComboBoxBehavior),
            new PropertyMetadata(false, null));

        public void Attach(DependencyObject associatedObject)
        {
            if (!(associatedObject is ComboBox cb))
            {
                throw new ArgumentException("ComboBoxBehavior can only be used with a ComboBox.");
            }

            this.AssociatedObject = associatedObject;
            if (IsNullable)
            {
                cb.Loaded += OnComboBoxLoaded;
                cb.SelectionChanged += OnSelectionChanged;
            }
        }

        private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            ApplyIsNullable(sender as ComboBox);
        }

        private void OnComboBoxLoaded(object sender, RoutedEventArgs e)
        {
            ApplyIsNullable(sender as ComboBox);
        }

        private void ApplyIsNullable(ComboBox comboBox)
        {
            comboBox.ApplyTemplate();
            var clearButton = GetClearButton(comboBox);
            if (clearButton != null)
            {
                clearButton.Click -= OnClearButtonClicked;
                clearButton.Click += OnClearButtonClicked;

                if (IsNullable && comboBox.SelectedIndex != -1)
                {
                    clearButton.Visibility = Visibility.Visible;
                }
                else
                {
                    clearButton.Visibility = Visibility.Collapsed;
                }
            }
        }

        private void OnClearButtonClicked(object sender, RoutedEventArgs e)
        {
            (this.AssociatedObject as ComboBox).SelectedIndex = -1;
        }

        private Button GetClearButton(DependencyObject reference)
        {

            for (int childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(reference); childIndex++)
            {
                var child = VisualTreeHelper.GetChild(reference, childIndex);

                if (child is Button && ((Button)child).Name == "ClearButton")
                {
                    return (Button)child;
                }

                var clearButton = GetClearButton(child);
                if (clearButton is Button)
                {
                    return clearButton;
                }
            }

            return null;
        }

        public void Detach()
        {
            ComboBox comboBox = null;
            if (this.AssociatedObject is ComboBox)
            {
                comboBox = this.AssociatedObject as ComboBox;
                comboBox.Loaded -= OnComboBoxLoaded;
                comboBox.SelectionChanged -= OnSelectionChanged;
            }
            this.AssociatedObject = null;
        }
    }

ComobBox的使用方法:

<ComboBox ItemsSource="{Binding Source={StaticResource ViewModel}, Path=Actors}"
                  Height="25" Width="300"
                  HorizontalAlignment="Left" VerticalAlignment="Top"
                  Style="{StaticResource ComboBoxWithClearButtonStyle}">
            <interactivity:Interaction.Behaviors>
                <behaviors:ComboBoxBehavior IsNullable="True"/>
            </interactivity:Interaction.Behaviors>
</ComboBox>