在我的WPF应用程序中,我有一个DataGrid,我希望用户能够过滤显示哪些行。过滤是这样实现的:在GUI上有一个ComboBox,它枚举了某些属性的可能值,让我们称之为SomeProperty
。当用户选择一个值(例如"Value1"
)时,DataGrid将仅显示item.SomeProperty == "Value1"
的项目。 DataGrid和ComboBox内容都来自数据库。
我希望用户能够通过SomeProperty
关闭过滤,所以我找了一种方法将"all"
项添加到ComboBox,返回null
并且我可以在我的过滤逻辑中使用。我发现了这个:
http://philondotnet.wordpress.com/2009/09/18/how-to-select-null-none-in-a-combobox-listbox-listview
这是一个包装类,它将空项添加到ComboBox
或类似项。由于我还在使用ComboBox.DisplayMemberPath
属性,因此我更改了
public static readonly DependencyProperty NullItemProperty = DependencyProperty.Register(
"NullItem", typeof(object), typeof(NullItemSelectorAdapter), new PropertyMetadata("(None)"));
到
public static readonly DependencyProperty NullItemProperty = DependencyProperty.Register(
"NullItem", typeof(NullItem), typeof(NullItemSelectorAdapter), new PropertyMetadata(new NullItem()));
并添加了这样一个类:
[TypeConverter(typeof(NullItemConverter))]
class NullItem: DynamicObject
{
private const string Text = "(all)";
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = Text;
return true;
}
public override bool TryConvert(ConvertBinder binder, out object result)
{
result = null;
return true;
}
}
public class NullItemConverter : TypeConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type sourceType)
{
return true;
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return null;
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return true;
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return NullItem.Instance;
}
}
为了能够像这样使用它(省略不相关的属性):
<view:NullItemSelectorAdapter ItemsSource="{Binding People}">
<ComboBox DisplayMemberPath="Name"/>
</view:NullItemSelectorAdapter>
<view:NullItemSelectorAdapter ItemsSource="{Binding Products}">
<ComboBox DisplayMemberPath="Description"/>
</view:NullItemSelectorAdapter>
等
(ItemsSource
中的对象是生成的类的实例,因此我无法覆盖其ToString
方法。)
当我调用Application.MainWindow.Show()
时,所有这些ComboBox都被实例化了,我得到了很多这样的错误:
System.Windows.Data Error: 23 : Cannot convert 'MyNamespace.View.NullItem' from type 'NullItem' to type 'MyModel.Product' for 'hu-HU' culture with default conversions; consider using Converter property of Binding. NotSupportedException:'System.NotSupportedException: TypeConverter cannot convert from MyNamespace.View.NullItem.
at System.ComponentModel.TypeConverter.GetConvertFromException(Object value)
at System.ComponentModel.TypeConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
at MS.Internal.Data.DefaultValueConverter.ConvertHelper(Object o, Type destinationType, DependencyObject targetElement, CultureInfo culture, Boolean isForward)'
System.Windows.Data Error: 7 : ConvertBack cannot convert value 'MyNamespace.View.NullItem' (type 'NullItem'). target element is 'ComboBox' (Name=''); target property is 'SelectedItem' (type 'Object') NotSupportedException:'System.NotSupportedException: TypeConverter cannot convert from MyNamespace.View.NullItem.
at MS.Internal.Data.DefaultValueConverter.ConvertHelper(Object o, Type destinationType, DependencyObject targetElement, CultureInfo culture, Boolean isForward)
at MS.Internal.Data.ObjectTargetConverter.ConvertBack(Object o, Type type, Object parameter, CultureInfo culture)
at System.Windows.Data.BindingExpression.ConvertBackHelper(IValueConverter converter, Object value, Type sourceType, Object parameter, CultureInfo culture)'
我指定的TypeConverter没有实例化,即使它应该符合reference sources of MS.Internal.Data.DefaultValueConverter。
这些错误不会导致程序崩溃(之后运行正常),但是即使在快速计算机上,它们也会在窗口内容呈现时引起明显的延迟。怎么能让这种延迟消失?
我主要对一个解决方案感兴趣,该解决方案不涉及在Converter
的用法上为每个Binding手动添加NullItemSelectorAdapter
,因为这很多。我希望通过在NullItemSelectorAdapter和NullItem类中进行黑客攻击来解决这个问题。
解决方案: 下面的Roel's answer是我采用的解决方案,因为它是一个让所提到的错误消失的单线技巧。但是adabyron's accepted answer是语义上更正确,更优雅的解决方案,您应该使用它。
答案 0 :(得分:2)
第二个建议,在OP明确表示他的客户坚持无效项目之后。 我很遗憾地说我再次忽略了你的一个要求,即SelectedItem为null。但是(正如我在第一个答案中以不同的方式所述),
null
(或者在幕后翘曲它所以似乎那样)只是没有为我而去。
从好的方面来说,由于过滤机制肯定在您的控制之下,我想您应该能够使用以下内容。如果您不想将行为添加到每个ComboBox,可以使用this code以隐式样式应用它。
我创建了一个ComboBox行为,它通常会插入“ - All - ”项,并在过滤中添加了一个IsNullItemSelected
属性,而不是SelectedItem == null
。
当前(已知)限制:我希望ItemsSource是一个IList,包含的项应该是字符串或具有无参数构造函数。
行为:
using System;
using System.Linq;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Reflection;
namespace WpfApplication1.Behaviors
{
public class NullableComboBoxBehavior : Behavior<ComboBox>
{
// IsNullValueSelected
public static readonly DependencyProperty IsNullValueSelectedProperty = DependencyProperty.Register("IsNullValueSelected", typeof(bool), typeof(NullableComboBoxBehavior), new PropertyMetadata(false));
public bool IsNullValueSelected { get { return (bool)GetValue(IsNullValueSelectedProperty); } set { SetValue(IsNullValueSelectedProperty, value); } }
private const string AllCaption = "- All -";
protected override void OnAttached()
{
DependencyPropertyDescriptor.FromProperty(ComboBox.ItemsSourceProperty, typeof(ComboBox))
.AddValueChanged(this.AssociatedObject, OnItemsSourceChanged);
DependencyPropertyDescriptor.FromProperty(ComboBox.SelectedItemProperty, typeof(ComboBox))
.AddValueChanged(this.AssociatedObject, OnSelectedItemChanged);
// initial call
OnItemsSourceChanged(this, EventArgs.Empty);
OnSelectedItemChanged(this, EventArgs.Empty);
}
private void OnSelectedItemChanged(object sender, EventArgs e)
{
var cbx = this.AssociatedObject;
// If the caption of the selected item is either "- All -" or no item is selected,
// set IsNullValueSelected to true
if (cbx.SelectedItem != null)
{
// get caption directly or by way of DisplayMemberPath
string caption = cbx.SelectedItem.GetType() == typeof(string) ?
(string)cbx.SelectedItem :
GetDisplayMemberProperty(cbx.SelectedItem).GetValue(cbx.SelectedItem).ToString();
if (caption == AllCaption || caption == null)
this.IsNullValueSelected = true;
else
this.IsNullValueSelected = false;
}
else
this.IsNullValueSelected = true;
}
private void OnItemsSourceChanged(object sender, EventArgs e)
{
var cbx = this.AssociatedObject;
// assuming an ItemsSource that implements IList
if (cbx.ItemsSource != null && (IList)cbx.ItemsSource != null)
{
Type T = cbx.ItemsSource.AsQueryable().ElementType;
object obj;
if (T == typeof(string))
obj = AllCaption; // set AllCaption directly
else if (T.GetConstructor(Type.EmptyTypes) != null)
{
// set AllCaption by way of DisplayMemberPath
obj = Activator.CreateInstance(T);
GetDisplayMemberProperty(obj).SetValue(obj, AllCaption);
}
else
throw new Exception("Only types with parameterless ctors or string are supported.");
// insert the null item
((IList)cbx.ItemsSource).Insert(0, obj);
// select first item (optional).
// If you uncomment this, remove the OnSelectedItemChanged call in OnAttached
//cbx.SelectedIndex = 0;
}
}
private PropertyInfo GetDisplayMemberProperty(object obj)
{
if (string.IsNullOrEmpty(this.AssociatedObject.DisplayMemberPath))
throw new Exception("This will only work if DisplayMemberPath is set.");
// get the property info of the DisplayMemberPath
return obj.GetType().GetProperty(this.AssociatedObject.DisplayMemberPath);
}
}
}
实现:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
xmlns:beh="clr-namespace:WpfApplication1.Behaviors"
Title="MainWindow" Height="350" Width="580">
<Window.DataContext>
<vm:ComboBoxResetViewModel />
</Window.DataContext>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" >
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" SelectedValue="{Binding SelectedValue}" DisplayMemberPath="Name" Margin="5,2" Width="150" >
<i:Interaction.Behaviors>
<beh:NullableComboBoxBehavior IsNullValueSelected="{Binding IsNullValueSelected, Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
</ComboBox>
<TextBlock Text="SelectedItem:" FontWeight="SemiBold" Margin="50,2,0,2" VerticalAlignment="Center" />
<TextBlock Text="{Binding SelectedItem.Name, FallbackValue='null'}" Foreground="Blue" Margin="5,2" VerticalAlignment="Center" />
<TextBlock Text="IsNullValueSelected:" FontWeight="SemiBold" Margin="30,2,0,2" VerticalAlignment="Center" />
<TextBlock Text="{Binding IsNullValueSelected}" Foreground="Blue" Margin="5,2" VerticalAlignment="Center" />
</StackPanel>
</Window>
视图模型:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfApplication1.ViewModels
{
public class ComboBoxResetViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<ItemViewModel> _items;
public ObservableCollection<ItemViewModel> Items { get { return _items; } set { _items = value; OnPropertyChanged("Items"); } }
private ItemViewModel _selectedItem;
public ItemViewModel SelectedItem { get { return _selectedItem; } set { _selectedItem = value; OnPropertyChanged("SelectedItem"); } }
private bool _isNullValueSelected;
public bool IsNullValueSelected { get { return _isNullValueSelected; } set { _isNullValueSelected = value; OnPropertyChanged("IsNullValueSelected"); } }
public ComboBoxResetViewModel()
{
this.Items = new ObservableCollection<ItemViewModel>()
{
new ItemViewModel() { Name = "Item 1" },
new ItemViewModel() { Name = "Item 2" },
new ItemViewModel() { Name = "Item 3" },
new ItemViewModel() { Name = "Item 4" },
new ItemViewModel() { Name = "Item 5" }
};
}
}
public class ItemViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _name;
public string Name { get { return _name; } set { _name = value; OnPropertyChanged("Name"); } }
}
}
答案 1 :(得分:1)
如果可以从MyModel.Product继承NullItem类,则转换将成功。或者,如果无法继承,请包装对象并绑定它们。
讨论后编辑: 如果更改将selecteditem绑定到object的属性类型,则错误将消失。
答案 2 :(得分:1)
虽然我可以理解零项目的方法,因为它已被多次使用,我发现它更加干净,以区别
因此不会“通过选择某事来选择任何东西”,特别是如果要求是SelectedItem为null。
我建议创建一个自定义控件,使用重置按钮扩展组合框:
自定义控件:
using System;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1.Controls
{
[TemplatePart(Name = "PART_ResetButton", Type = typeof(Button))]
public class ComboBoxReset : ComboBox
{
private Button _resetButton;
// reset event (not used in this demo case, but should be provided)
public static readonly RoutedEvent ResetEvent = EventManager.RegisterRoutedEvent("Reset", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ComboBoxReset));
public event RoutedEventHandler Reset { add { AddHandler(ResetEvent, value); } remove { RemoveHandler(ResetEvent, value); } }
private void OnReset()
{
RoutedEventArgs args = new RoutedEventArgs(ResetEvent);
RaiseEvent(args);
}
public ComboBoxReset()
{
// lookless control, get default style from generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(typeof(ComboBoxReset), new FrameworkPropertyMetadata(typeof(ComboBoxReset)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (this.Template != null)
{
// find reset button in template
Button btn = this.Template.FindName("PART_ResetButton", this) as Button;
if (_resetButton != btn)
{
// detach old handler
if (_resetButton != null)
_resetButton.Click -= ResetButton_Click;
_resetButton = btn;
// attach new handler
if (_resetButton != null)
_resetButton.Click += ResetButton_Click;
}
}
}
private void ResetButton_Click(object sender, RoutedEventArgs e)
{
// reset the selected item and raise the event
this.SelectedItem = null;
OnReset();
}
}
}
对于样式,基本上只需通过VS设计器获取普通ComboBox的默认模板,添加按钮(在下面的代码中查找PART_ResetButton
),更改TargetType(到ComboBoxReset
),把它放在Themes \ generic.xaml中。不是很多。这就是风格对我的影响:
<Style x:Key="ComboBoxFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="4,4,21,4" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<LinearGradientBrush x:Key="ButtonNormalBackground" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#F3F3F3" Offset="0"/>
<GradientStop Color="#EBEBEB" Offset="0.5"/>
<GradientStop Color="#DDDDDD" Offset="0.5"/>
<GradientStop Color="#CDCDCD" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ButtonNormalBorder" Color="#FF707070"/>
<Geometry x:Key="DownArrowGeometry">M 0 0 L 3.5 4 L 7 0 Z</Geometry>
<Style x:Key="ComboBoxReadonlyToggleButton" TargetType="{x:Type ToggleButton}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="ClickMode" Value="Press"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Themes:ButtonChrome x:Name="Chrome" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}" SnapsToDevicePixels="true">
<Grid HorizontalAlignment="Right" Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}">
<Path x:Name="Arrow" Data="{StaticResource DownArrowGeometry}" Fill="Black" HorizontalAlignment="Center" Margin="3,1,0,0" VerticalAlignment="Center"/>
</Grid>
</Themes:ButtonChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter Property="RenderPressed" TargetName="Chrome" Value="true"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Fill" TargetName="Arrow" Value="#AFAFAF"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<LinearGradientBrush x:Key="TextBoxBorder" EndPoint="0,20" MappingMode="Absolute" StartPoint="0,0">
<GradientStop Color="#ABADB3" Offset="0.05"/>
<GradientStop Color="#E2E3EA" Offset="0.07"/>
<GradientStop Color="#E3E9EF" Offset="1"/>
</LinearGradientBrush>
<Style x:Key="ComboBoxEditableTextBox" TargetType="{x:Type TextBox}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="MinWidth" Value="0"/>
<Setter Property="MinHeight" Value="0"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer x:Name="PART_ContentHost" Background="Transparent" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="ClickMode" Value="Press"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Themes:ButtonChrome x:Name="Chrome" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}" RoundCorners="false" SnapsToDevicePixels="true" Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}">
<Path x:Name="Arrow" Data="{StaticResource DownArrowGeometry}" Fill="Black" HorizontalAlignment="Center" Margin="0,1,0,0" VerticalAlignment="Center"/>
</Themes:ButtonChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter Property="RenderPressed" TargetName="Chrome" Value="true"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Fill" TargetName="Arrow" Value="#AFAFAF"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="ComboBoxEditableTemplate" TargetType="{x:Type ComboBox}">
<Grid x:Name="Placement" SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
<Themes:SystemDropShadowChrome x:Name="Shdw" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=Placement}">
<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>
<ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Grid>
</ScrollViewer>
</Border>
</Themes:SystemDropShadowChrome>
</Popup>
<Themes:ListBoxChrome x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="2" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderFocused="{TemplateBinding IsKeyboardFocusWithin}"/>
<TextBox x:Name="PART_EditableTextBox" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsReadOnly="{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}" Margin="{TemplateBinding Padding}" Style="{StaticResource ComboBoxEditableTextBox}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
<ToggleButton Grid.Column="1" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ComboBoxToggleButton}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="true">
<Setter Property="Foreground" Value="Black"/>
</Trigger>
<Trigger Property="IsDropDownOpen" Value="true">
<Setter Property="RenderFocused" TargetName="Border" Value="true"/>
</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>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true"/>
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</MultiTrigger>
<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="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>
<Style TargetType="{x:Type ctrl:ComboBoxReset}">
<Setter Property="FocusVisualStyle" Value="{StaticResource ComboBoxFocusVisual}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
<Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
<Setter Property="BorderBrush" Value="{StaticResource ButtonNormalBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="Padding" Value="4,3"/>
<Setter Property="Height" Value="22"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="ScrollViewer.PanningMode" Value="Both"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ctrl:ComboBoxReset}">
<Grid x:Name="MainGrid" SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Width="0"/>
<ColumnDefinition Width="Auto" />
</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">
<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>
<ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Grid>
</ScrollViewer>
</Border>
</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}"/>
<Button x:Name="PART_ResetButton" Grid.Column="2" Margin="2,0,0,0" >
<Image Stretch="Uniform" Source="/WpfApplication1;component/Resources/remove.png" />
</Button>
</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>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true"/>
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</MultiTrigger>
<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.Triggers>
<Trigger Property="IsEditable" Value="true">
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Padding" Value="3"/>
<Setter Property="Template" Value="{StaticResource ComboBoxEditableTemplate}"/>
</Trigger>
</Style.Triggers>
</Style>
实施(制作上面的截图):
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:WpfApplication1.Controls"
xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:ComboBoxResetViewModel />
</Window.DataContext>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" >
<ctrl:ComboBoxReset ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" DisplayMemberPath="Name" Margin="5,2" Width="150" />
<TextBlock Text="SelectedItem:" FontWeight="SemiBold" Margin="50,2,0,2" VerticalAlignment="Center" />
<TextBlock Text="{Binding SelectedItem.Name, FallbackValue='null'}" Margin="5,2" VerticalAlignment="Center" />
</StackPanel>
</Window>
最后我用于测试的视图模型:
using System.Collections.Generic;
using System.ComponentModel;
namespace WpfApplication1.ViewModels
{
public class ComboBoxResetViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private List<ItemViewModel> _items;
public List<ItemViewModel> Items { get { return _items; } set { _items = value; OnPropertyChanged("Items"); } }
private ItemViewModel _selectedItem;
public ItemViewModel SelectedItem { get { return _selectedItem; } set { _selectedItem = value; OnPropertyChanged("SelectedItem"); } }
public ComboBoxResetViewModel()
{
this.Items = new List<ItemViewModel>()
{
new ItemViewModel() { Name = "Item 1" },
new ItemViewModel() { Name = "Item 2" },
new ItemViewModel() { Name = "Item 3" },
new ItemViewModel() { Name = "Item 4" },
new ItemViewModel() { Name = "Item 5" }
};
}
}
public class ItemViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _name;
public string Name { get { return _name; } set { _name = value; OnPropertyChanged("Name"); } }
}
}