我想在 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
,但这就是我遇到的问题。
有什么想法如何做到这一点吗?或者更好的方法,比如依附行为?
比这里的一些回答更详细一些,但是我不想在我的回答中有任何代码或者视图模型的改变。我写这个是作为一个WPF的行为。当附加到XAML时,它会在视觉中注入一个按钮。它会将默认值设置为-1(或者您可以调整为其他默认值)。这是一个可重复使用的控件,很容易在整个项目中添加到您的XAML上。希望这有所帮助。如果发现错误,欢迎反馈。
产生的视觉:
所选项目:
行为代码:
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>
我使用下面的解决方案来解决类似的问题。它利用绑定的转换器属性在内部表示(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();
}
考虑为“无”组合框项实现空对象模式,并将此项添加到项列表中。然后实现自定义逻辑以在该类中保存 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