CustomControl与ComboBox

时间:2012-12-02 12:57:23

标签: c# .net wpf combobox custom-controls

我在使用ComboBox创建自定义控件时遇到了麻烦 这是我的简单代码:

public class MyComboBox : Control
{
    public IEnumerable ItemsSource
    {
        get { return (IEnumerable)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ItemsSource.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(MyComboBox), new UIPropertyMetadata(null));




    public string DisplayMemberPath
    {
        get { return (string)GetValue(DisplayMemberPathProperty); }
        set { SetValue(DisplayMemberPathProperty, value); }
    }

    // Using a DependencyProperty as the backing store for DisplayMemberPath.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DisplayMemberPathProperty =
        DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(MyComboBox), new UIPropertyMetadata(""));





    public string SelectedValuePath
    {
        get { return (string)GetValue(SelectedValuePathProperty); }
        set { SetValue(SelectedValuePathProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SelectedValuePath.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedValuePathProperty =
        DependencyProperty.Register("SelectedValuePath", typeof(string), typeof(MyComboBox), new UIPropertyMetadata(""));





    public object SelectedValue
    {
        get { return (object)GetValue(SelectedValueProperty); }
        set { SetValue(SelectedValueProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SelectedValue.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedValueProperty =
        DependencyProperty.Register("SelectedValue", typeof(object), typeof(MyComboBox), new UIPropertyMetadata(null));




    public int SelectedIndex
    {
        get { return (int)GetValue(SelectedIndexProperty); }
        set { SetValue(SelectedIndexProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SelectedIndex.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedIndexProperty =
        DependencyProperty.Register("SelectedIndex", typeof(int), typeof(MyComboBox), new UIPropertyMetadata(0));



    static MyComboBox()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MyComboBox), new FrameworkPropertyMetadata(typeof(MyComboBox)));
    }
}

这是它的Generic.xaml:

<Style TargetType="{x:Type local:MyComboBox}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:MyComboBox}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Column="0" Text="MyComboBox" />
                    <ComboBox Grid.Column="1"
                              ItemsSource="{Binding Path=ItemsSource, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                              SelectedIndex="{Binding Path=SelectedIndex, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                              DisplayMemberPath="{Binding Path=DisplayMemberPath, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                              SelectedValuePath="{Binding Path=SelectedValuePath, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                              SelectedValue="{Binding Path=SelectedValue, RelativeSource={RelativeSource Mode=TemplatedParent}}">
                    </ComboBox>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

为了测试它,我用这个MainWindow.xaml创建了一个简单的WPF应用程序:

<Window x:Class="Example.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Example"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <ComboBox Grid.Row="0" Grid.Column="0" Margin="4" VerticalAlignment="Center"
                  ItemsSource="{Binding Path=Numbers}"
                  DisplayMemberPath="Key"
                  SelectedValuePath="Value"
                  SelectedValue="{Binding Path=Number, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock Grid.Row="0" Grid.Column="1" Margin="4" HorizontalAlignment="Center" VerticalAlignment="Center"
                   Text="{Binding Path=Number, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

        <local:MyComboBox Grid.Row="1" Grid.Column="0" Margin="4" VerticalAlignment="Center"
                          ItemsSource="{Binding Path=MyNumbers}"
                          DisplayMemberPath="Key"
                          SelectedValuePath="Value"
                          SelectedValue="{Binding Path=MyNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock Grid.Row="1" Grid.Column="1" Margin="4" HorizontalAlignment="Center" VerticalAlignment="Center"
                   Text="{Binding Path=MyNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    </Grid>
</Window>

和这个ViewModel:

public class ViewModel : INotifyPropertyChanged
{
    private int _number;
    public int Number
    {
        get { return _number; }
        set
        {
            _number = value;
            OnPropertyChanged("Number");
        }
    }

    public Dictionary<string, int> Numbers { get; set; }

    private int _myNumber;
    public int MyNumber
    {
        get { return _myNumber; }
        set
        {
            _myNumber = value;
            OnPropertyChanged("MyNumber");
        }
    }

    public Dictionary<string, int> MyNumbers { get; set; }

    public ViewModel()
    {
        Numbers = new Dictionary<string, int>()
        {
            { "One", 1 },
            { "Two", 2 },
            { "Three", 3 }
        };
        Number = 1;

        MyNumbers = new Dictionary<string, int>()
        {
            { "Four", 4 },
            { "Five", 5 },
            { "Six", 6 }
        };
        MyNumber = 4;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler e = PropertyChanged;
        if (e != null)
        {
            e(this, new PropertyChangedEventArgs(name));
        }
    }
}

当我启动它时,我的自定义控件有一个红色边框,Visual Studio的输出窗口发出此错误信号:

System.Windows.Data Error: 23 : Cannot convert '[Four, 4]' from type 'KeyValuePair`2' to type 'System.Int32' for 'en-US' culture with default conversions; consider using Converter property of Binding. NotSupportedException:'System.NotSupportedException: Int32Converter cannot convert from System.Collections.Generic.KeyValuePair`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].
   at System.ComponentModel.TypeConverter.GetConvertFromException(Object value)
   at System.ComponentModel.TypeConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
   at System.ComponentModel.BaseNumberConverter.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 '[Four, 4]' (type 'KeyValuePair`2'). BindingExpression:Path=MyNumber; DataItem='ViewModel' (HashCode=55591935); target element is 'MyComboBox' (Name=''); target property is 'SelectedValue' (type 'Object') NotSupportedException:'System.NotSupportedException: Int32Converter cannot convert from System.Collections.Generic.KeyValuePair`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].
   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)'

当我从Generic.xaml中删除属性SelectedIndex时,问题就消失了,但是我需要它,因为我想要谁将使用我的控件,可以具有与ComboBox相同的基本功能。
任何人都知道如何解决它?

2 个答案:

答案 0 :(得分:1)

我自己找到解决方案 问题出在我为SelectedIndex插入的默认值:它必须是-1而不是0。

答案 1 :(得分:0)

起初,在我看来你的问题出现在SelectedValue中。在你的VM中,它的类型为int,但WPF希望它是KeyValuePair。集合项目的类型。

ItemsSource="{Binding Path=MyNumbers}" // KeyValuePair
     DisplayMemberPath="Key" 
     SelectedValuePath="Value"
     SelectedValue="{Binding Path=MyNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" // int

然后我意识到我的错误,SelectedItem必须是KeyValuePair类型。 错误消息看起来WPF不会查看SelectedValuePath给出的items属性,但会尝试将其显式转换为KeyValue对。这不是记录在案的行为。