【问题标题】:How to do multiple items Data binding in WPF如何在 WPF 中进行多项数据绑定
【发布时间】:2014-03-04 08:55:03
【问题描述】:

我有一个特定的场景。我的应用程序看起来像这样。

在左侧有一些用户列表,这是一个列表框,在右侧有几个字段是数据绑定到左侧。它的工作原理是,如果您在右侧选择“用户 1”,则会出现用户 1 的相关信息,您可以修改该信息,并且它是与 "UpdateSourceTrigger=PropertyChanged" 的数据绑定,因此它也会立即反映在左侧。其他用户的情况相同。

现在的问题是,如果我选择多个用户并编辑字段 3,它是可编辑的文本框。现在,如果我选择用户 1 并编辑此文本框,它会反映在用户 1“注意:...”中,如果我选择用户 2 并编辑字段 3,它会更新用户 2“注意:...”但如果多选我如何实现呢?假设我想同时选择用户 1 和用户 2 并编辑注释字段它应该更新用户 1 和用户 2 的注释字段并且数据绑定也应该工作我的意思是它应该立即将我输入到文本框中的文本。有什么想法可以实现吗?

目前在我的 viewModel 中

型号

public String Note
        {
            get
            {
                return (String)GetValue(NoteProperty);
            }
            set { SetValue(NoteProperty, value); }
        }

查看

在 XAML 中,用户列表框项模板是这样定义的

<TextBlock Text="{Binding Note, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

在 XAML 中,右侧的文本框(字段 3)以相同的方式绑定数据

<TextBox Text="{Binding Note, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"  />

如何实现多用户数据绑定?

请帮助并给我一些想法。

编辑:

转换器:

public class MultiBindingConverter : IValueConverter
{
    ObservableCollection<Info> mycollection;

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var coll = (ObservableCollection<Info>)value;
        mycollection = coll;
        if (coll.Count == 1)
        {
            if (parameter.ToString() == "FNote")
                return coll[0];
        }
        else if (coll.Count > 1)
        {
            // string name = coll[0].FirstName;
            if (parameter.ToString() == "FNote")
            {
                string name = coll[0].Note;
                foreach (var c in coll)
                {
                    if (c.Note != name)
                        return null;
                    else continue;
                }
                return name;
            }
        }
        return null;
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {

        if (parameter.ToString() == "FNote")
        {
            foreach (var c in mycollection)
            {
                c.Note = value.ToString();
            }
            return mycollection;
        }
        return null;
    }
}

对我来说,只有一个 TextBox Editable NoteTextBox 需要与多个用户进行数据绑定。

在我的视图模型中

我写过

视图模型

private Command selectionChangedCommand;
        public Command SelectionChangedCommand
        {
            get
            {
                if (selectionChangedCommand == null)
                {
                    selectionChangedCommand = new Command(SelectionChanged, true);
                }
                return selectionChangedCommand;
            }
            set { selectionChangedCommand = value; }
        }
        public void SelectionChanged(object value)
        {
            selectedItem =  new ObservableCollection<Info>((value as IEnumerable).OfType<Info>());
            
        }
        private ObservableCollection<Info> selectedItem;

        public ObservableCollection<Info> SelectedItem
        {
            get { return selectedItem; }
            set
            {
                selectedItem = value;
                PropertyChanged("SelectedItem");
            }
        }

Info 类中有一个属性Note 需要绑定到视图的两个位置。

【问题讨论】:

  • 如果您需要在Field3 中添加多个注释,那么您必须包含一个单独的逻辑来分隔字段,以便您可以确定哪个注释来自哪个用户。跨度>
  • 我不明白为什么需要单独登录?我将选择说两个用户并更新它将反映在 user1 和 user2 字段中的注释字段。
  • 我可以为 user1、user2 和 user3 设置不同的注释吗?
  • 因为选择更改时需要填充注释字段。您填充它的内容取决于有多少选择。这是逻辑,不是直接绑定。
  • 这里有一个转换器可以帮助您实现特定属性的自定义绑定。

标签: c# wpf xaml mvvm data-binding


【解决方案1】:

我完全同意@GazTheDestroyer ...这种数据绑定不能仅通过数据绑定来实现。 @Kumar 建议作为 POC 工作,但是当您在一个实时项目中并且您使用模型、视图模型和视图以及具有一个视图模型的多个 UserControl 或具有两个 ViewModel 的一个用户控件时,实现这种场景的难度超乎想象。

好吧,没有更多的理论。我已经实现了这一点,我将分享我是如何做到的。

一对一的 DataBinding 非常完美并且工作正常。当您选择用户 4 这个用户Note 字段和Field3 Editable NoteBox 绑定到同一个属性,所以它完美地工作。

In multiple selection say User4 is selected first, then you select User3 and user1, I put a logic in code behind that when multiple items are selected Note text is empty.这并不反对 MVVM 作为基于某些视图标准更新视图并没有破坏 MVVM 模式。所以现在当可编辑文本框用一些文本更新时,user4 属性在 viewModel 中更新。现在困难的部分是更新其他选定的用户。这是将更新所选用户的代码,并将反映我提到的Mode="TwoWay", UpdateSourceTriger="PropertyChanged"

if (listUser.SelectedItems.Count > 1)
{
  for (int i = 0; i < listUser.SelectedItems.Count; i++)
    {
     Info info = listUser.SelectedItems[i] as Info;
     info.Note = (string)tbNote.Text;
    }
}

这样Editable note textbox的值会在所有用户Note Property的属性中更新,并且由于绑定是双向的,它也会反映在其他用户中。

可能有很多方法可以解决它,但我发现了这种方法并且效果很好,所以我想我会回答我自己的问题。

【讨论】:

    【解决方案2】:

    您无法仅通过数据绑定来实现这一点,因为在某些情况下您需要做出合乎逻辑的决定。

    例如,如果 user1 和 user2 有不同的注释文本,那么当两者都被选中时,您不能同时显示两者。相反,我猜你想要一些方法来指定你想要“保留原始文本”,或者允许用户过度输入以将两个文本设置为相同。

    无论您打算如何,您都需要在视图模型中拥有单独的绑定源,以便您可以独立更新它们并做出合乎逻辑的决策。

    【讨论】:

    • 在这种情况下,当用户 1 和用户 2 或用户 3 都被默认选中时,注释字段将为空,我输入的任何内容都会反映在所有三个用户的注释字段中。最后一部分我没有得到兄弟“您的视图模型中的单独绑定源”......相同的控件如何具有单独的绑定源。对WPF知之甚少
    • 我可以想到各种复杂的触发绑定可能会起作用,但我真的认为你最好在视图模型中明确地这样做。它只会在视图中变得太乱。另外,您也可以对其进行单元测试。
    • 你能提供任何这样的例子吗?触发器绑定将在哪里工作,用于多用户选择?
    【解决方案3】:

    我尝试了一些我知道的东西,我得到了符合您要求的输出。如果我错了,请纠正我。

    XAML

    <Window x:Class="MVVM_sample_ListBox.MainWindow"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:MVVM_sample_ListBox"
                Title="MainWindow" Height="350" Width="525"
                xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
            <Window.Resources>
                <local:Converter x:Key="Converter"/>
            </Window.Resources>    
           <Grid>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="235*" />
              <ColumnDefinition Width="268*" />
             </Grid.ColumnDefinitions>
              <ListBox x:Name="lb"  SelectionMode="Multiple" Grid.Row="0"  ItemsSource="{Binding MyCollection}">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="MouseUp" >
                            <i:InvokeCommandAction CommandParameter="{Binding SelectedItems, ElementName=lb}" Command="{Binding SelectionChangedCommand}"/>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <StackPanel>
                                <TextBlock Text="{Binding FirstName}"/>
                                <TextBlock Text="{Binding SecondName}"/>
                                <TextBlock Text="{Binding Company}"/>
                                
                            </StackPanel>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
                <StackPanel Grid.Column="1" >
                    <TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Text="{Binding SelectedItem,ConverterParameter=FName, Converter={StaticResource Converter}}" Name="textBox1" VerticalAlignment="Top" Width="120" />
                    <TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Text="{Binding SelectedItem,ConverterParameter=SName, Converter={StaticResource Converter}}" Name="textBox2" VerticalAlignment="Top" Width="120" />
                    <TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Text="{Binding SelectedItem,ConverterParameter=Comp, Converter={StaticResource Converter}}" Name="textBox3" VerticalAlignment="Top" Width="120" />
                </StackPanel>
           </Grid>
    </Window>
    
        
        
        
    

    C#

        public partial class MainWindow : Window
            {
                public MainWindow()
                {
                    InitializeComponent();
                    this.DataContext = new ViewModel();
                }
        
            }
    

    型号

            public class Model : INotifyPropertyChanged
            {
                private string fname;
        
                public string FirstName
                {
                    get { return fname; }
                    set { fname = value;RaisePropertyChanged("FirstName"); }
                }
        
                private string sname;
        
                public string SecondName
                {
                    get { return sname; }
                    set { sname = value; RaisePropertyChanged("SecondName");}
                }
        
                private string company;
        
                public string Company
                {
                    get { return company; }
                    set { company = value;RaisePropertyChanged("Company"); }
                }
        
        
                public event PropertyChangedEventHandler PropertyChanged;
                private void RaisePropertyChanged(string name)
                {
                    if(PropertyChanged!= null)
                    {
                        this.PropertyChanged(this,new PropertyChangedEventArgs(name));
                    }
                }
            }
    

    视图模型

            public class ViewModel : INotifyPropertyChanged
            {
                private MyCommand selectionChangedCommand;
        
                public MyCommand SelectionChangedCommand
                {
                    get 
                    {
                        if (selectionChangedCommand == null)
                        {
                            selectionChangedCommand = new MyCommand(SelectionChanged);
                        }
                        return selectionChangedCommand;
                    }
                    set { selectionChangedCommand = value; }
                }
                public void SelectionChanged(object value)
                {
                    SelectedItem = new ObservableCollection<Model>((value as IEnumerable).OfType<Model>());
                }
               
        
                private ObservableCollection<Model> selectedItem;
        
                public ObservableCollection<Model> SelectedItem
                {
                    get { return selectedItem; }
                    set { selectedItem = value; RaisePropertyChanged("SelectedItem"); }
                }
        
                private ObservableCollection<Model> mycoll;
        
            public ObservableCollection<Model> MyCollection
            {
                get { return mycoll;}
                set { mycoll = value;}
            }
                public ViewModel()
                {
                    SelectedItem = new ObservableCollection<Model>();
                    SelectedItem.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(SelectedItem_CollectionChanged);
                    MyCollection = new ObservableCollection<Model>();
                    MyCollection.Add(new Model { FirstName = "aaaaa", SecondName = "bbbbb", Company = "ccccccc" });
                    MyCollection.Add(new Model { FirstName = "ddddd", SecondName = "bbbbb", Company = "eeeeeee" });
                    MyCollection.Add(new Model { FirstName = "fffff", SecondName = "gggggg", Company = "ccccccc" });
        
                }
        
                void SelectedItem_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
                {
                    //this.SelectedItem =new ObservableCollection<Model>((sender as ObservableCollection<Model>).Distinct());
                }
                public event PropertyChangedEventHandler PropertyChanged;
                private void RaisePropertyChanged(string name)
                {
                    if(PropertyChanged!= null)
                    {
                        this.PropertyChanged(this,new PropertyChangedEventArgs(name));
                    }
                }
            }
            public class MyCommand : ICommand
            {
                private Action<object> _execute;
        
                private Predicate<object> _canexecute;
        
                public MyCommand(Action<object> execute, Predicate<object> canexecute)
                {
                    _execute = execute;
                    _canexecute = canexecute;
                }
        
                public MyCommand(Action<object> execute)
                    : this(execute, null)
                {
                    _execute = execute;
                }
        
                #region ICommand Members
        
                public bool CanExecute(object parameter)
                {
                    if (parameter == null)
                        return true;
                    if (_canexecute != null)
                    {
                        return _canexecute(parameter);
                    }
                    else
                    {
                        return true;
                    }
        
                }
        
                public event EventHandler CanExecuteChanged
                {
                    add { CommandManager.RequerySuggested += value; }
                    remove { CommandManager.RequerySuggested -= value; }
                }
        
        
                public void Execute(object parameter)
                {
                    _execute(parameter);
                }
        
                #endregion
            }
    

    转换器

            public class Converter : IValueConverter
            {
                ObservableCollection<Model> mycollection;
                public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
                {
                    var coll = (ObservableCollection<Model>)value;
                    mycollection = coll;
                    if (coll.Count == 1)
                    {
                        if (parameter.ToString() == "FName")
                            return coll[0].FirstName;
                        else if (parameter.ToString() == "SName")
                            return coll[0].SecondName;
                        else if (parameter.ToString() == "Comp")
                            return coll[0].Company;
                    }
                    else if(coll.Count >1)
                    {
                       // string name = coll[0].FirstName;
                        if (parameter.ToString() == "FName")
                        {
                            string name = coll[0].FirstName;
                            foreach (var c in coll)
                            {
                                if (c.FirstName != name)
                                    return null;
                                else continue;
                            }
                            return name;
                        }
                        if (parameter.ToString() == "SName")
                        {
                            string name = coll[0].SecondName;
                            foreach (var c in coll)
                            {
                                if (c.SecondName != name)
                                    return null;
                                else continue;
                            }
                            return name;
                        }
                        if (parameter.ToString() == "Comp")
                        {
                            string name = coll[0].Company;
                            foreach (var c in coll)
                            {
                                if (c.Company != name)
                                    return null;
                                else continue;
                            }
                            return name;
                        }
                    }
                    return null;
                }
        
                public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
                {
                    
                    if (parameter.ToString() == "FName")
                    {
                        foreach (var c in mycollection)
                        {
                            c.FirstName = value.ToString();
                        }
                        return mycollection;
                    }
                    else
                    if (parameter.ToString() == "SName")
                    {
                        foreach (var c in mycollection)
                        {
                            c.SecondName = value.ToString();
                        }
                        return mycollection;
                    }
                    else
                    if (parameter.ToString() == "Comp")
                    {
                        foreach (var c in mycollection)
                        {
                            c.Company = value.ToString();
                        }
                        return mycollection;
                    }
                    return null;
                }
            }
    
       
    

    【讨论】:

    • 优秀的 kumar,你救了我的命,非常感谢,只是为了给运行时绑定的感觉,我把 UpdateSourceTrigger=PropertyChanged, 放在三个文本框和三个文本块中。
    • 嗨@kumar,如果你有时间我已经更新了我的代码以便更好地理解,如果我只想用一个属性说Note那么我该怎么做?我有一个模型,Info,我将它放入可观察的集合中,我唯一改变的是ConverterParameter= "FNote",但它不起作用。有什么想法吗?
    猜你喜欢
    • 2011-05-14
    • 1970-01-01
    • 1970-01-01
    • 2018-03-14
    • 2011-01-19
    • 1970-01-01
    • 2017-08-30
    • 2011-07-19
    相关资源
    最近更新 更多