当WPF ComboBox的选择为空时,能否显示替代文本?

10

你好!

我希望我的 WPF ComboBox 在其数据绑定的选择为 null 时显示一些替代文本。

视图模型具有预期属性:

public ThingoSelectionViewModel : INotifyPropertyChanged {
    public ThingoSelectionViewModel(IProvideThingos) {
        this.Thingos = IProvideThingos.GetThingos();
    }

    public ObservableCollection<Thingo> Thingos { get; set; }

    public Thingo SelectedThingo { 
        get { return this.selectedThingo; }
        set { // set this.selectedThingo and raise the property change notification
    }

    // ...

}
视图以预期方式将 XAML 绑定到视图模型:
<ComboBox x:Name="ComboboxDrive" SelectedItem="{Binding Path=SelectedThingo}"
          IsEditable="false" HorizontalAlignment="Left" MinWidth="100" 
          IsReadOnly="false" Style="{StaticResource ComboboxStyle}"
          Grid.Column="1" Grid.Row="1" Margin="5" SelectedIndex="0">
    <ComboBox.ItemsSource>
        <CompositeCollection>
        <ComboBoxItem IsEnabled="False">Select a thingo</ComboBoxItem>
        <CollectionContainer 
            Collection="{Binding Source={StaticResource Thingos}}" />
        </CompositeCollection>
    </ComboBox.ItemsSource>
</ComboBox>
ComboBoxItem嵌入到顶部是一种在顶部获得额外项的方法。这纯粹只是界面设计:视图模型保持简单。唯一的问题是:当ComboBox的选择为空时,用户想要显示"选择一个thingo"。

用户不希望默认选择一个thingo。他们希望看到一条消息告诉他们选择一个thingo。

我想避免通过在视图模型中添加ThingoWrapper类来污染代码,如果其.ActualThingo属性为空,则返回"Select a thingo"的ToString方法,包装每个Thingo以填充Thingos,并找出防止用户选择空的Thingo的方法。

是否有一种使用纯XAML或纯XAML和几行代码的方法可以在ComboBox边界内显示"选择一个thingo"?


顺便说一句:最终我实现了ThingoWrapper,修改了ThingoSelectionViewModel以处理包装的null值的选择,并找到了自动选择Whatsy和Fadoozamy对象的方法,这样我就不必再对它们进行包装了。 - Garth Kidd
7个回答

8

你对MVVM的要求有多严格?在视图中是否可以有一些代码?

也许你可以将ComboBox放在一个网格中,像这样:

<Grid>
    <ComboBox x:Name="ComboBoxControl"
              SelectionChanged="ComboBoxControl_SelectionChanged"
              HorizontalAlignment="Left" VerticalAlignment="Top" 
              MinWidth="{Binding ElementName=UnselectedText, Path=ActualWidth}">
        <ComboBoxItem>One</ComboBoxItem>
        <ComboBoxItem>Two</ComboBoxItem>
        <ComboBoxItem>Three</ComboBoxItem>
    </ComboBox>
    <TextBlock IsHitTestVisible="False" 
               x:Name="UnselectedText" 
               HorizontalAlignment="Left" 
               Text="Select an option..." 
               VerticalAlignment="Top" Margin="4" 
               Padding="0,0,30,0" />
</Grid>

然后,在代码后台中,在事件处理程序中插入一些逻辑:

Private Sub ComboBoxControl_SelectionChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs)
    If ComboBoxControl.SelectedIndex = -1 Then
        UnselectedText.Visibility = Windows.Visibility.Visible
    Else
        UnselectedText.Visibility = Windows.Visibility.Hidden
    End If
End Sub

IsHitTestVisible="False"依赖属性设置在TextBlock上,可以让鼠标事件穿过去,这样你就可以点击ComboBox了。在代码后台将可见性设置为Hidden可以使默认ComboBox的布局在隐藏提示文本时不会跳动。


1
即使你想以那种方式做,也不必破坏你的MVVM。只需使用转换器将选择转换为可见性即可。 - Ben

5

您不能使用控件模板触发器,但是您可以为组合框设置一个简单的项模板:

<ComboBox ItemsSource="{Binding}" >
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <TextBlock x:Name="displayText" Text="{Binding}" />
                <DataTemplate.Triggers>
                    <DataTrigger Binding="{Binding}" Value="{x:Null}">
                        <Setter TargetName="displayText" Property="Text" Value="Default Value" />
                    </DataTrigger>
                </DataTemplate.Triggers>
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox>

嗯,我也得检查一下这个。 - Garth Kidd

4

编辑: 看起来触发器的想法行不通。我尝试将以下内容添加到测试组合框的控件模板中,但没有效果:

    <Trigger Property="SelectedItem" Value="{x:Null}">
        <Setter Property="Text" Value="No Item Selected"/>
    </Trigger>

此外,在Blend中尝试编辑控件模板(编辑当前)时,我只得到了一个没有颜色的功能缺失组合框,只有一个丑陋的按钮(但是有一个无边框下拉菜单)。可以尝试其他人的建议(也许是Mike Brown的建议)。
在控件模板中可以使用触发器。以下是我正在开发的应用程序中使用ListBox的示例。
<ControlTemplate x:Key="SnazzyFormListBoxTemplate" TargetType="{x:Type ListBox}">
    <Microsoft_Windows_Themes:ClassicBorderDecorator x:Name="Bd" SnapsToDevicePixels="True" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderStyle="Sunken" BorderThickness="{TemplateBinding BorderThickness}">
        <ScrollViewer Padding="{TemplateBinding Padding}" Focusable="False" Template="{DynamicResource SnazzyScrollViewerControlTemplate}">
            <Grid>
            <TextBlock x:Name="textBlock" Text="No Items" FontFamily="Arial" FontWeight="Bold" FontSize="13.333" Foreground="#4D000000" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,10"/>
            <ItemsPresenter x:Name="itemsPresenter" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            </Grid>
        </ScrollViewer>
    </Microsoft_Windows_Themes:ClassicBorderDecorator>
    <ControlTemplate.Triggers>
        <Trigger Property="Selector.IsSelected" Value="True"/>
        <Trigger Property="HasItems" Value="False">
            <Setter Property="Visibility" TargetName="textBlock" Value="Visible"/>
            <Setter Property="Visibility" TargetName="itemsPresenter" Value="Collapsed"/>
        </Trigger>
        <Trigger Property="HasItems" Value="True">
            <Setter Property="Visibility" TargetName="textBlock" Value="Collapsed"/>
            <Setter Property="Visibility" TargetName="itemsPresenter" Value="Visible"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

以上控件模板具有一个触发器,检查属性HasItems。如果为False,则在ListBox中央显示文本块,上面写着“没有项目”。如果有项目,则会显示它们。

在您的情况下,更改触发器以检查是否ItemSelected为x:Null,并将Text属性设置为“未选择任何内容”。


用户正在询问如何在组合框中设置“未选择任何内容”的值,而不是为一个空的组合框设置不同的外观。 - Michael Brown
我知道。他们可以在控件模板中设置一个触发器,检查所选项目的状态,如果为空,则将框的文本值设置为“未选择任何内容”。我希望用户能够从给定的示例中推断出来。我更新了我的答案以更好地适应这一点。 - CodeWarrior
@Mike Brown:我已经编辑了我的答案,所以如果您能取消踩一下的话,我会很感激。 - CodeWarrior

3
这里我发现的最简单的解决方案是使用Null Object模式。以.NET Framework中静态值Double.NaN为例,如果你为你的Thingo创建了一个Null Object,在你的视图模型中,你可以将其附加到列表的前面,表示“未选择任何内容”。为Thingo类创建一个DataTemplate,并为Null Object实例创建一个DataTrigger,显示“选择一个值”。
我可以提供代码示例,但现在已经过了我的睡觉时间。

1

我知道这是一个旧的线程,但这就是我的做法。在获取Thingos集合后,我只需插入一个新的Thingo,其ID值为虚假值,显示值为“选择一个thingo”。

    public ThingoSelectionViewModel(IProvideThingos) {
            this.Thingos = IProvideThingos.GetThingos();
            Thingo newThingo = new Thingo();
            newThingo.ThingoID = -1;
            newThingo.ThingoDisplayName = "Select a thingo";
            this.Thingos.Insert(0, newThingo);
        }

现在,当ComboBox数据绑定时,第一项是“选择一个thingo”。然后当选择一个Thingo时,我测试SelectedThingo的ID,并相应地采取行动。

这基本上就是空对象模式。我有时会使用类的静态属性来公开空对象,以便人们可以将其与Thingo.NotSpecified进行比较。 - Garth Kidd
数字...我经常被问到是否使用某种方法或流程,我想答案是“不”。只是后来发现答案是“是”,但我只是不知道它的正式名称。谢谢。 - D'Hag

0

我知道我在挖掘一个旧帖子,但这是我在谷歌搜索中找到的第一个。 在Visual Studio中,您可以选择将默认选择设置为0,而不是-1,并且只需将第一个选择作为默认文本即可。

<ComboBox x:name="ThingoSelector" SelectedIndex="0">
    <ComboBoxItem IsEnabled="False">Choose Thingo</ComboBoxItem>
    <ComboBoxItem>Thingo 1</ComboBoxItem>
</ComboBox>

0

另外一个选择:

<ComboBox>
  <ComboBoxItem Visibility="Collapsed" IsSelected="True">
    <TextBlock Text="Choose item" />
  </ComboBoxItem>
  <ComboBoxItem>
    <TextBlock Text="Item 1" />
  </ComboBoxItem>
  <ComboBoxItem>
    <TextBlock Text="Item 2" />
  </ComboBoxItem>
</ComboBox>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接