当前位置: 首页 > 知识库问答 >
问题:

WPF:带有重置项目的组合框

孔睿
2023-03-14

我想在 WPF 中创建一个顶部有一个项的组合框,当它被选中时,选定项应设置为 null(重置为默认状态)。我一直在寻找,但没有找到令人满意的解决方案。

如果可能的话,我希望它只使用XAML代码或附加的行为来实现,因为我不太喜欢更改视图的ViewModel中的内容,或者重写标准控件。

这是我到目前为止想出的(缩短代码):

[...]
<Popup x:Name="PART_Popup" [...]>
    <Border x:Name="PopupBorder" [...]>
        <ScrollViewer x:Name="DropDownScrollViewer" [...]>
            <StackPanel [...]>
                <ComboBoxItem>(None)</ComboBoxItem>
                <ItemsPresenter x:Name="ItemsPresenter"/>
            </StackPanel>
        </ScrollViewer>
    </Border>
</Popup>
[...]

我认为最好的方法是以某种方式添加一个事件触发器,当项目被选中时,该触发器将SelectedIndex设置为-1,但这就是我遇到的问题。

有什么想法如何做到这一点吗?或者更好的方法,比如依附行为?

共有3个答案

桑博远
2023-03-14

比这里的一些回答更详细一些,但是我不想在我的回答中有任何代码或者视图模型的改变。我写这个是作为一个WPF的行为。当附加到XAML时,它会在视觉中注入一个按钮。它会将默认值设置为-1(或者您可以调整为其他默认值)。这是一个可重复使用的控件,很容易在整个项目中添加到您的XAML上。希望这有所帮助。如果发现错误,欢迎反馈。

  1. 没有外部引用,你可以将其与代码一起使用,而不能使用其他DLL。 (嗯,它确实使用System.Windows.Interactivity,但大多数在WPF应用程序中都有这个)
  2. 可在整个应用中重复使用
  3. 风格将符合您的主题。
  4. 你可以随心所欲地顶起它
  5. 我知道这是一个将近 6 年的历史主题(截至我在 2019 年撰写本文时),但如果您喜欢它 - 请让它成为答案,因为没有!

产生的视觉:

所选项目:

行为代码:

public class ComboBoxClearBehavior : Behavior<ComboBox>
{
    private Button _addedButton;
    private ContentPresenter _presenter;
    private Thickness _originalPresenterMargins;

    protected override void OnAttached()
    {
        // Attach to the Loaded event. The visual tree at this point is not available until its loaded.
        AssociatedObject.Loaded += AssociatedObject_Loaded;

        // If the user or code changes the selection, re-evaluate if we should show the clear button
        AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;

        base.OnAttached();
    }

    protected override void OnDetaching()
    {
        // Its likely that this is already de-referenced, but just in case the visual was never loaded, we will remove the handler anyways.
        AssociatedObject.Loaded -= AssociatedObject_Loaded;
        base.OnDetaching();
    }

    private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        EvaluateDisplay();
    }

    /// <summary>
    /// Checks to see if the UI should show a Clear button or not based on what is or isn't selected.
    /// </summary>
    private void EvaluateDisplay()
    {
        if (_addedButton == null) return;
        _addedButton.Visibility = AssociatedObject.SelectedIndex == -1 ? Visibility.Collapsed : Visibility.Visible;

        // To prevent the text or content from being overlapped by the button, adjust the margins if we have reference to the presenter.
        if (_presenter != null)
        {
            _presenter.Margin = new Thickness(
                _originalPresenterMargins.Left, 
                _originalPresenterMargins.Top, 
                _addedButton.Visibility == Visibility.Visible ? ClearButtonSize + 6 : _originalPresenterMargins.Right, 
                _originalPresenterMargins.Bottom);
        }
    }

    private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
    {
        // After we have loaded, we will have access to the Children objects. We don't want this running again.
        AssociatedObject.Loaded -= AssociatedObject_Loaded;

        // The ComboBox primary Grid is named  MainGrid. We need this to inject the button control. If missing, you may be using a custom control.
        if (!(AssociatedObject.FindChild("MainGrid") is Grid grid)) return;

        // Find the content presenter. We need this to adjust the margins if the Clear icon is present.
        _presenter = grid.FindChildren<ContentPresenter>().FirstOrDefault();
        if (_presenter != null) _originalPresenterMargins = _presenter.Margin;

        // Create the new button to put in the view
        _addedButton = new Button
        {
            Height = ClearButtonSize, 
            Width = ClearButtonSize,
            HorizontalAlignment = HorizontalAlignment.Right
        };


        // Find the resource for the button - In this case, our NoChromeButton Style has no button edges or chrome
        if (Application.Current.TryFindResource("NoChromeButton") is Style style)
        {
            _addedButton.Style = style;
        }

        // Find the resource you want to put in the button content
        if (Application.Current.TryFindResource("RemoveIcon") is FrameworkElement content)
        {
            _addedButton.Content = content;
        }

        // Hook into the Click Event to handle clearing
        _addedButton.Click += ClearSelectionButtonClick;

        // Evaluate if we should display. If there is nothing selected, don't show.
        EvaluateDisplay();

        // Add the button to the grid - First Column as it will be right justified.
        grid.Children.Add(_addedButton);
    }

    private void ClearSelectionButtonClick(object sender, RoutedEventArgs e)
    {
        // Sets the selected index to -1 which will set the selected item to null.
        AssociatedObject.SelectedIndex = -1;
    }

    /// <summary>
    /// The Button Width and Height. This can be changed in the Xaml if a different size visual is desired.
    /// </summary>
    public int ClearButtonSize { get; set; } = 15;
}

用法:

<ComboBox 
 ItemsSource="{Binding SomeItemsSource, Mode=OneWay}"
 SelectedValue="{Binding SomeId, Mode=TwoWay}"
 SelectedValuePath="SomeId">
  <i:Interaction.Behaviors>
    <behaviors:ComboBoxClearBehavior />
  </i:Interaction.Behaviors>
</ComboBox>

这种行为需要两样东西——你可能已经有了,但它们是:

1.)按钮模板-代码正在寻找样式。在我的例子中,它被称为NoChromeButton-如果你正在寻找一个交钥匙解决方案,你可以将我的添加到你的资源文件中:

<Style x:Key="NoChromeButton"
       TargetType="{x:Type Button}">
    <Setter Property="Background"
            Value="Transparent" />
    <Setter Property="BorderThickness"
            Value="1" />
    <Setter Property="Foreground"
            Value="{DynamicResource WindowText}" />
    <Setter Property="HorizontalContentAlignment"
            Value="Center" />
    <Setter Property="VerticalContentAlignment"
            Value="Center" />
    <Setter Property="Cursor"
            Value="Hand"/>
    <Setter Property="Padding"
            Value="1" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Grid x:Name="Chrome"
                      Background="{TemplateBinding Background}"
                      SnapsToDevicePixels="true">
                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                      Margin="{TemplateBinding Padding}"
                                      RecognizesAccessKey="True"
                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsEnabled"
                             Value="false">
                        <Setter Property="Foreground"
                                Value="#ADADAD" />
                        <Setter Property="Opacity"
                                TargetName="Chrome"
                                Value="0.5" />
                    </Trigger>
                    <Trigger
                        Property="IsMouseOver"
                        Value="True">
                        <Setter
                            TargetName="Chrome"
                            Property="Background"
                            Value="{DynamicResource ButtonBackgroundHover}" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

此外,您还需要您的图标才能清除。如果您有一个,只需更新代码以使用该资源(名为“RemveIcon”)。其他wze…这是我的:

<Viewbox x:Key="RemoveIcon"
         x:Shared="False"
         Stretch="Uniform">
    <Canvas Width="58"
            Height="58">
        <Path Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Control, Mode=FindAncestor}}">
            <Path.Data>
                <PathGeometry Figures="M 29 0 C 13 0 0 13 0 29 0 45 13 58 29 58 45 58 58 45 58 29 58 13 45 0 29 0 Z M 43.4 40.6 40.6 43.4 29 31.8 17.4 43.4 14.6 40.6 26.2 29 14.6 17.4 17.4 14.6 29 26.2 40.6 14.6 43.4 17.4 31.8 29 Z"
                              FillRule="NonZero" />
            </Path.Data>
        </Path>
    </Canvas>
</Viewbox>
蔡默
2023-03-14

我使用下面的解决方案来解决类似的问题。它利用绑定的转换器属性在内部表示(null是一个合理的值)和我想在ComboBox中显示的内容之间来回移动。我喜欢不需要在模型或视图模型中添加显式列表,但我不喜欢转换器中的字符串文字和ComboBox中的字符串文字之间脆弱的连接。

<ComboBox SelectedValue="{Binding MyProperty, Converter={x:Static Converters:MyPropertySelectionConverter.Instance}}" >
    <ComboBox.ItemsSource>
        <CompositeCollection>
            <sys:String>(none)</sys:String>
            <CollectionContainer Collection="{Binding Source={x:Static Somewhere}, Path=ListOfPossibleValuesForMyProperty}" />
        </CompositeCollection>
    </ComboBox.ItemsSource>
</ComboBox>

然后转换器看起来像:

public class MyPropertySelectionConverter : IValueConverter
{
    public static MyPropertySelectionConverter Instance
    {
        get { return s_Instance; }
    }

    public const String NoneString = "(none)";

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Object retval = value as MyPropertyType;
        if (retval == null)
        {
            retval = NoneString;
        }
        return retval;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Object retval = null;
        if (value is MyPropertyType)
        {
            retval = value;
        }
        else if (String.Equals(NoneString, value as String, StringComparison.OrdinalIgnoreCase))
        {
            retval = null;
        }
        else
        {
            retval = DependencyProperty.UnsetValue;
        }
        return retval;
    }


    private static MyPropertySelectionConverter s_Instance = new MyPropertySelectionConverter();
}
傅明知
2023-03-14

考虑为“无”组合框项实现空对象模式,并将此项添加到项列表中。然后实现自定义逻辑以在该类中保存 null 对象,或者只检查所选项是否为 NullItem 类型。

 类似资料:
  • 我在后台有< code>ViewModel(实现< code > INotifyPropertyChanged )和类< code>Category,它只有一个< code>string类型的属性。我的ComboBox SelectedItem绑定到类别的实例。当我更改instance的值时,SelectedItem没有更新,Combobox也没有更改。 编辑:代码 组合框: 物业: 我尝试的是:

  • 我没有在.xaml中指定任何特殊的内容,所以我将省略该部分。除此之外,我希望能够使用Name属性设置SelectedItem。我非常希望答案在代码后面,但如果它愚蠢地复杂,只有xaml的答案也一样好。我没有MVVM的任何经验,我可以假设它会被建议。随着我对WPF的深入研究,我当然会扩展我在这方面的知识,但现在我只想让它发挥作用 这不是家庭作业 编辑:忘记列出我也会遇到的错误 系统.Windows。

  • 我正在我的 WPF 应用程序中使用 ComboBox 并遵循 MVVM。有一个字符串列表,我想在我的组合框中显示。 XAML: 查看模型: 现在这段代码运行得非常好。我可以从视图中选择,我可以在视图模型中获得更改,如果我从视图模型中更改SelectedItem,我可以在视图中看到它。 这就是我想要达到的目标。当我从我的视图中更改选定的项目时,我需要检查值是好/坏(或任何东西)设置选定的项目,否则不

  • 问题内容: 我的印象是,除了选择列表中已经存在的任何值之外,您还可以键入一个组合框。但是,我似乎找不到有关如何执行此操作的信息。我需要添加一个属性以允许输入文本吗? 问题答案: 在此之前(请参见下面的注释),您将提供其他元素供人们输入自己的选项。 该机制 可在所有浏览器中使用,并且不需要JavaScript 。 如果选择了“其他”选项,您可以使用一点JavaScript来聪明地仅显示。 该元素旨在

  • 日安, 我希望我的组合框选择其中的第一个项目。我正在使用C#和WPF。我从DataSet读取数据。要填充组合框: 组合框XAML代码: 如果我尝试: 它不起作用。

  • 新加入WPF的我正尝试使用TreeView,它提供的灵活性给我留下了难以置信的印象。 到目前为止,我的每个treeview项目都实现了一个扩展器,这是显示每个项目更详细信息并让标题仅显示摘要的好方法。但是,这不适合叶项目,并且浪费了屏幕空间——我的treeview中的每个叶项目(其中有许多)显示的数据量相对较小。 我想要为叶项目实现的是水平包装,而不是垂直列表。设想显示一个网格或(stackpan