我想将 ListView 绑定到字符串的可观察集合,我想使用来自两个可视控件的数据(例如,这些字符串的前缀和后缀)对其进行修改。
一个简化的示例:
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;
}
}
为了保持列表视图的更新,我目前只是在 CollectionChanged 事件上重置其 ItemsSource:
void sources_CollectionChanged(...)
{
lvTarget.ItemsSource = Items();
}
但我也希望 ListView 绑定到其三个源中的任何一个中的更改:集合和前缀/后缀控件。我想我想要一个多重绑定或多数据触发器,但我不能完全理解语法和我能找到的所有示例将控件绑定到其他控件,同时我也有那个 ObservableCollection 作为源。
附言对不起,如果它简单明了,这只是我使用 WPF 的第三天,我有点不知所措!谢谢!
可观察集合用于在集合中的项目发生更改(例如添加、删除、移动等)时发出通知,并且如果您更改字符串中的文本,它也不会通知。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-在视图的构造函数中,放置以下代码来初始化视图的数据上下文:
public MainWindow()
{
this.DataContext= new SomethingViewModel();
}
4-最后将视图元素绑定到视图模型属性:
<TextBox Text={Binding Prefix,Mode=TwoWay}/>
<TextBox Text={Binding Postfix,Mode=TwoWay}/>
<ListView ItemsSource={Binding Items}/>
5-如果要更改源中的项,请不要初始化新对象,只需使用此方法:
Sources.Clear();
Sources.Add();
免责声明:请注意,如果您只需要修改项目的视觉外观,那么使用 DataTemplate(如 Clemens 的答案)将是更好的解决方案。如果要实际将组合字符串作为项目,请使用 ViewModel 方式。以下解决方案不是最佳做法,它试图演示多重绑定的工作原理。
这个问题最好在你的视图模型中解决。转换器(尤其是多功能转换器)只应在绝对必要时使用。
但是,由于这是您使用 WPF 的第 3 天,因此您还不应该为 MVVM 而烦恼。
-
使 Window 类成为自身的 DataContext:
public MainWindow() { InitializeComponent(); this.DataContext = this; }
这将允许我们使用从窗口到其上定义的属性的数据绑定。
-
我们可以为我们的项使用默认属性,但 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 更改不会重新触发多重绑定,因此我们仅使用
IEnumerable<string>
作为类型。 -
现在,让我们在 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>
好的,我们使用多重绑定来设置项目源。这实质上是执行您之前在更改处理程序中执行的操作:每当其子绑定之一发生更改时,它都会调用指定的转换器并使用其结果更新 ItemsSource。但这是什么
PrefixPostfixConverter
? -
添加 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
参数)获取输入,并将组合值创建为数组。
你不需要任何代码。只需为 ListView 项创建适当的数据模板:
<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)。