WPF ComboBox绑定行为

时间:2009-09-27 14:13:21

标签: wpf data-binding combobox

我有以下XAML标记:

<TextBox x:Name="MyTextBox" Text="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Products}" DisplayMemberPath="ProductName"
    SelectedValue="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber}"
    SelectedValuePath="ProductNumber" />

My View的DataContext绑定到包含名为SelectedCustomer的公共属性的viewmodel。 Customer对象包含Product类型的FavouriteProduct属性,Product对象包含公共属性ProductNumber和ProductName。

我正在寻找的行为是让ComboBox的SelectedItem更新TextBox中的Text,反之亦然。 ComboBox到TextBox的工作正常。选择ComboBox中的任何产品会使用该产品的产品编号更新TextBox。然而,当我试着走另一条路时,我得到了一种奇怪的行为。它仅适用于所选项目之前的项目。我会试着解释一下:

考虑以下产品清单([产品编号],[产品名称]):

  1. 芬达
  2. 百事可乐
  3. 可口可乐
  4. 的Sprite
  5. 现在让我们说SelectedCustomer最喜欢的产品是可口可乐(必须是开发商)。因此,当窗口打开时,TextBox读取3,ComboBox读取可口可乐。可爱。现在让我们将TextBox中的产品编号更改为2. ComboBox将其值更新为百事可乐。现在尝试将TextBox中的产品编号更改为高于可口可乐(3)的数字。不太可爱。选择4(Sprite)或5(Water)会使ComboBox恢复为可口可乐。因此,行为似乎是从ItemSource中的列表中打开窗口宽度的项目下方的任何内容都不起作用。将其设置为1(芬达),其他任何一个都不起作用。将它设置为5(水),它们都可以工作。这可能与ComboBox的初始化有关吗?潜在的错误?好奇,如果有其他人看到过这种行为。

    更新

    在阅读Mike Brown的回复后,我为SelectedProduct和SelectedProductNumber创建了属性。我遇到的问题是,只要从ComboBox中选择一些内容,就会在无限循环中结束,其中属性保持相互更新。我是否错误地实现了OnPropertyChanged处理程序或者是否存在我遗漏的内容?以下是我的ViewModel代码片段:

    private int _SelectedProductNumber = -1;
            public int SelectedProductNumber
            {
                get
                {
                    if (_SelectedProductNumber == -1 && SelectedCustomer.Product != null)
                        _SelectedProductNumber = SelectedCustomer.Product.ProductNumber;
                    return _SelectedProductNumber;
                }
                set
                {
                    _SelectedProductNumber = value;
                    OnPropertyChanged("SelectedProductNumber");
                    _SelectedProduct = ProductList.FirstOrDefault(s => s.ProductNumber == value);
                }
            }
    
            private Product _SelectedProduct;
            public Product SelectedProduct
            {
                get
                {
                    if (_SelectedProduct == null)
                        _SelectedProduct = SelectedCustomer.Product;
                    return _SelectedProduct;
                }
                set
                {
                    _SelectedProduct = value;
                    OnPropertyChanged("SelectedProduct");
                    _SelectedProductNumber = value.ProductNumber;
                }
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected virtual void OnPropertyChanged(string property)
            {
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
    

    更新2

    我现在通过从两个属性更新SelectedCustomer.FavouriteProduct然后在读取它们时使用它来稍微改变了实现。这现在有效,但我不确定这是'正确的方法'。

    private int _SelectedProductNumber = 0;
    public int SelectedProductNumber
    {
        get
        {
            if (SelectedCustomer.Product != null)
                _SelectedProductNumber = SelectedCustomer.Product.ProductNumber;
            return _SelectedProductNumber;
        }
        set
        {
            _SelectedProductNumber = value;
            SelectedCustomer.FavouriteProduct = ProductList.FirstOrDefault(s => s.ProductNumber == value);
            OnPropertyChanged("SelectedProductNumber");
            OnPropertyChanged("SelectedProduct");
        }
    }
    
    private Product _SelectedProduct;
    public Product SelectedProduct
    {
        get
        {
            if (SelectedCustomer.Product != null)
                _SelectedProduct = SelectedCustomer.Product;
            return _SelectedProduct;
        }
        set
        {
            _SelectedProduct = value;
            SelectedCustomer.FavouriteProduct = value;
            OnPropertyChanged("SelectedProduct");
            OnPropertyChanged("SelectedProductNumber");
        }
    }
    

3 个答案:

答案 0 :(得分:1)

你的目标不太清楚,所以我写了这些文章以支持我能看到的任何选项。

要将两个元素绑定到一个项目同步,您可以在组合框上设置IsSynchronizedWithCurrentItem="True",如下所示:

<TextBox x:Name="MyTextBox" Text="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Products}" DisplayMemberPath="ProductName"
    SelectedValue="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber}"
    IsSynchronizedWithCurrentItem="True"
    SelectedValuePath="ProductNumber" />

这意味着绑定到同一个背景对象的当前窗口中的所有内容都将保持同步,并且不会给出您所看到的奇怪行为。

此报价表格更长MSDN article描述了效果:

  

IsSynchronizedWithCurrentItem   属性很重要,当时   选择的变化,就是这样   改变“当前项目”   关注窗口。这说明   这个对象的WPF引擎   将用于改变当前   项目。没有这个属性,   DataContext中的当前项不会   更改,因此您的文本框   将假设它仍然在   列表中的第一项。

然后按照另一个答案的建议设置Mode=TwoWay只会确保在更新文本框时更新基础对象以及更新文本框时更新对象。

这会使文本框编辑所选项目文本,而不是选择具有匹配文本的组合列表中的项目(这可能是您可能想要实现的替代方案?)

要实现同步选择效果,可能需要在组合框上设置IsEditable="True"以允许用户在文本框中键入项目和删除文本框。或者,如果您需要两个框,则使用带有IsSynchronizedWithCurrentItem="True"IsEditable="True"的第二个组合框替换文本框,然后设置样式以使其像文本框一样。

答案 1 :(得分:1)

您要做的是在ViewModel上显示当前所选产品和当前所选产品编号的单独属性。更改所选产品后,请更新产品编号,反之亦然。所以你的viewmodel应该看起来像这样

public class MyViewModel:INotifyPropertyChanged
{
    private Product _SelectedProduct;
    public Product SelectedProduct
    {
        get { return _SelectedProduct; }
        set
        {
            _SelectedProduct = value;
            PropertyChanged(this, new PropertyChangedEventArgs("SelectedProduct"));
            _SelectedProductID = _SelectedProduct.ID;
            PropertyChanged(this, new PropertyChangedEventArgs("SelectedProductID"));
        }
    }
    private int _SelectedProductID;
    public int SelectedProductID
    {
        get { return _SelectedProductID; }
        set
        {
            _SelectedProductID = value;
            PropertyChanged(this, new PropertyChangedEventArgs("SelectedProductID")); 
            _SelectedProduct = _AvailableProducts.FirstOrDefault(p => p.ID == value); 
            PropertyChanged(this,new PropertyChangedEventArgs("SelectedProduct"));
        }
    }  
    private IEnumerable<Product> _AvailableProducts = GetAvailableProducts();

    private static IEnumerable<Product> GetAvailableProducts()
    {
        return new List<Product>
                   {
                       new Product{ID=1, ProductName = "Coke"},
                       new Product{ID = 2, ProductName="Sprite"},
                       new Product{ID = 3, ProductName = "Vault"},
                       new Product{ID=4, ProductName = "Barq's"}
                   };
    }

    public IEnumerable<Product> AvailableProducts
    {
        get { return _AvailableProducts; }
    }  
    private Customer _SelectedCustomer; 
    public Customer SelectedCustomer
    {
        get { return _SelectedCustomer; } 
        set
        {
            _SelectedCustomer = value; 
            PropertyChanged(this, new PropertyChangedEventArgs("SelectedCustomer")); 
            SelectedProduct = value.FavoriteProduct;
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

所以现在你的XAML绑定到适当的属性,viewModel负责syncrhronization

<TextBox 
  x:Name="MyTextBox" 
  Text="{Binding Path=SelectedProductID, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox 
  x:Name="MyComboBox" 
  ItemsSource="{Binding AvailableProducts}" 
  DisplayMemberPath="ProductName" 
  SelectedItem="{Binding SelectedProduct}" />

不要忘记实现其余的INotifyPropertyChanged和GetAvailableProducts函数。也可能有一些错误。我在这里输入了这个而不是使用VS,但你应该得到一般的想法。

答案 2 :(得分:0)

尝试: SelectedItem =“{Binding Path = YourPath,Mode = TwoWay”} 而不是设置SelectedValue和SelectedValuePath。

也可以使用SelectedValue,不要忘记Mode = TwoWay,因为这不是默认值。

一个好的approuch将使用主细节模式 - 将主(项目视图,例如组合框)绑定到数据源集合和详细视图(例如文本框)到源集合中的选定项目,使用绑定转换器来读/写相应的属性。

这是一个例子: http://blogs.microsoft.co.il/blogs/tomershamam/archive/2008/03/28/63397.aspx

请注意,主绑定的格式为{Binding}或{Binding SourceCollection},详细信息绑定的格式为{Binding}或{Binding SourceCollection}。

要使其正常工作,您需要使用保留所选项目的对象包装集合。 WPF具有以下内置之一:ObjectDataProvider。

实施例: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/068977c9-95a8-4b4a-9d38-b0cc36d06446