将ListView绑定到ObservableCollection和两个可视控件

时间:2014-12-29 08:02:34

标签: c# wpf xaml listview data-binding

我想将ListView绑定到一个ObservableCollection字符串,我想用两个可视控件的数据修改这些字符串(例如,这些字符串的前缀和后缀)。

简化示例:

XAML:

<TextBox Name="tbPrefix"/>
<TextBox Name="tbPostfix"/>

<ListView Name="lvTarget"/>

C#:

public ObservableCollection<string> sources = GetFromSomewhere();

public IEnumerable<string> Items()
{
    foreach (var source in sources) 
    {
        yield return tbPrefix.Text + source + tbPostfix.Text;
    }
}

为了保持ListView的更新,我目前只是在CollectionChanged事件上重置其ItemsSource:

void sources_CollectionChanged(...)
{
    lvTarget.ItemsSource = Items();
}

但我也希望ListView绑定到其三个源中任何的更改:集合和前缀/后缀控件。我想我想要一个MultiBinding或一个MultiDataTrigger,但是我无法完全理解语法和我能找到的所有示例都将控件绑定到其他控件,而我也将ObservableCollection作为一个源。

P.S。对不起,如果它简单明了,那只是我第三天使用WPF,我有点不知所措!谢谢!

3 个答案:

答案 0 :(得分:1)

免责声明:请注意,如果您只需要修改项目的视觉外观,使用DataTemplate(如Clemens的答案)将是更好的解决方案。如果您想将组合字符串实际作为项目,请使用ViewModel方式。以下解决方案不是最佳实践,并试图演示MultiBindings的工作原理。


这个问题最好在ViewModel中解决。转换器(特别是MultiConverters)只应在绝对必要时使用。

但是因为这是你在WPF的第3天,你不应该对MVVM感到困扰。

  1. 使您的Window类成为其自身的DataContext:

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    }
    

    这将让我们使用Window中的数据绑定到其上定义的属性。

  2. 我们可以为项目使用默认属性,但是当属性值更改时,WPF将不会注意到。我们现在将使用DependencyProperty

    public static readonly DependencyProperty ItemsProperty
        = DependencyProperty.Register("Items", typeof (IEnumerable<string>), typeof (MainWindow));
    
    public IEnumerable<string> Items
    {
        get { return (IEnumerable<string>) GetValue(ItemsProperty); }
        set { SetValue(ItemsProperty, value); }
    }
    
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    
        this.Items = new[] {"sdf", "fdsa", "tgrg"};
    }
    

    每当我们调用此属性的setter时,WPF都会注意到它并更新对此属性的所有绑定。我们还更新了构造函数,以便最初加载一些值。

    我们也可以实现INotifyPropertyChanged - 事实上,ViewModels使用这种模式 - 和/或使用了ObservableCollection。不幸的是,ObservableCollection更改不会重新触发MultiBinding,所以我们只使用IEnumerable<string>作为类型。

  3. 现在让我们在XAML中添加绑定:

    <StackPanel>
        <TextBox Name="prefixTextBox" />
        <TextBox Name="postfixTextBox" />
        <ListBox>
            <ListBox.ItemsSource>
                <MultiBinding>
                    <MultiBinding.Converter>
                        <wpfApplication1:PrefixPostfixConverter />
                    </MultiBinding.Converter>
                    <Binding Path="Items" />
                    <Binding ElementName="prefixTextBox" Path="Text" />
                    <Binding ElementName="postfixTextBox" Path="Text" />
                </MultiBinding>
            </ListBox.ItemsSource>
        </ListBox>
    </StackPanel>
    

    好的,我们使用MultiBinding设置ItemsSource。这基本上是你在改变处理程序中做的事情:每当它的一个子绑定发生变化时,它会调用指定的转换器并用其结果更新ItemsSource。但这是什么PrefixPostfixConverter

  4. 添加PrefixPostfixConverter类:

    public class PrefixPostfixConverter : IMultiValueConverter 
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values == null || values.Length != 3)
                throw new ArgumentException("values");
    
            var items = values[0] as IEnumerable;
            var prefix = values[1] as string;
            var postfix = values[2] as string;
    
            if (items == null || prefix == null || postfix == null)
                return null;
    
            return items.Cast<object>()
                        .Select(i => prefix + i + postfix)
                        .ToArray();
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
    

    这将获取三个绑定的输入(以values参数形式出现),并将组合值创建为数组。

答案 1 :(得分:1)

可观察集合用于通知集合中的项目何时更改,例如添加,删除,移动等...如果更改字符串中的文本,它将不会通知。 WPF中的第一个最佳实践规则,即使用ViewModel绑定属性而不是代码隐藏。您可以通过以下方式解决此问题:
1-创建一个名为SomethingViewModel的新类
2-添加到它所有属性都需要绑定到您的视图:

public class SomethingViewModel : INotifyPropertyChanged
{
    private string _prefix;
    private string _postfix;

    public SomethingViewModel()
    {
        Sources = new ObservableCollection<string>(/*pass initial data of the list*/);
        Sources.CollectionChanged += (sender, args) => OnPropertyChanged("Items");
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    private ObservableCollection<string> Sources { get; set; }

    public IList<string> Items
    {
        get { return Sources.Select(x => string.Format("{0}{1}{2}", Prefix, x, Postfix)).ToList(); }
    }

    public string Prefix
    {
        get
        {
            return _prefix;
        }
        set
        {
            if (_prefix == value) return;
            _prefix = value;
            OnPropertyChanged("Prefix");
            OnPropertyChanged("Items");
        }
    }

    public string Postfix
    {
        get
        {
            return _postfix;
        }
        set
        {
            if (_postfix == value) return;
            _postfix = value;
            OnPropertyChanged("Postfix");
            OnPropertyChanged("Items"); // we will notify that the items list has changed so the view refresh its items
        }
    }
}

3-在View的构造函数中,将以下代码用于初始化视图的datacontext:

public MainWindow()
{
    this.DataContext= new SomethingViewModel();
}

4 - 最后将视图元素绑定到viewmodel属性:

<TextBox Text={Binding Prefix,Mode=TwoWay}/>
<TextBox  Text={Binding Postfix,Mode=TwoWay}/>

<ListView ItemsSource={Binding Items}/>

5 - 如果要更改源中的项目,请不要初始化新对象,只需使用以下方法:

Sources.Clear();
Sources.Add();

答案 2 :(得分:0)

您不需要任何代码。只需为ListView项创建一个合适的DataTemplate:

<ListView ItemsSource="{Binding Items}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Text, ElementName=tbPrefix}"/>
                <TextBlock Text="{Binding}"/>
                <TextBlock Text="{Binding Text, ElementName=tbPostfix}"/>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

另请注意,Items是公共属性,而不是方法,您应该在视图模型类中声明它。此视图模型类的实例将分配给视图的DataContext(例如MainWindow)。