Question

I have a particular scenarios. My application looks like this. enter image description here

In the left side there are some User list Which is a ListBox and at the right side few fields which are data binding to left side. How it works is, if you select "User 1" in the right side user 1 related information will appear and you can modify the information and its is data binding with "UpdateSourceTrigger=PropertyChanged" so it immediately reflects at the left side too. Same case for other users.

Now the problem is if I select multiple users and edit a field say Field 3 which is Editable a textBox. Now If I select user 1 and edit this textbox it reflects in the user 1 "Note: ... " and if I select user 2 and edit the Field 3 it updates the User 2 "Note: ... " but in case of multi selection How do I achieve it? Suppose I want to select user 1 and User 2 both and Edit the Note field It should update both the note fields of user 1 and user 2 and Data binding should also work I mean it should immediately the text i am entering into the textbox. Any ideas how can I achieve this?

Currently in my viewModel

Model

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

View

and in XAML the User ListBox Items template is defined like this

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

and in the XAML the rightside textbox (field 3) is data bound in the same manner

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

How do I achieve multiple users data binding?

Please help and give me some ideas.

EDIT:

Converter:

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;
    }
}

For me only one TextBox Editable NoteTextBox needs to to be DataBinded with multiple Users.

In my ViewModel

I have written

ViewModel

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");
            }
        }

In the Info class there is one property Note which needs to be binded to the View's two places.

Was it helpful?

Solution

I fully agree with @GazTheDestroyer ... this kind of Data Binding can not be achieved through Data binding alone. What @Kumar has suggested is working as a POC, but when you are in a live project and you play with model, viewModel and view and many UserControl with one view model or one User control with two ViewModels, then the difficulty of achieving this scenario is beyond guessing.

Ok, no more theory. I have achieved this and I am going to share how I did so.

One-to-one DataBinding is perfect and working fine. When you select User 4 This user Note field and Field3 Editable NoteBox are bound to the same Property, so it works perfectly.

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. This is not against MVVM as updating a view based on some criteria of view is not breaking MVVM pattern. So now when the editable text box is updated with some text user4 properties is updated in viewModel. Now the difficult part is to update the other selected users. Here is the code that will update the selected users and will reflect as I have mentioned 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;
    }
}

In this way the value of the Editable note textbox is updated in the properties of all the users Note Property and as the binding is two-way, it will reflect in other users too.

There might be many way to solve it, but I found this way and it's working superbly, so I thought I'd answer my own question.

OTHER TIPS

You cannot achieve this via databinding alone, since there are situations where you need to make logical decisions.

For instance, if user1 and user2 have different notetext, then when both are selected you cannot show both at the same time. Instead I guess you want some method of specifying that you want to "keep original text", or allow user to over type to set both texts to be the same.

Whatever you intend, you need to have separate binding sources in your viewmodel so that you can update them independently and make logical decisions.

I tried something with i know and i got output just as your requirement.Please correct me if i'm wrong.

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();
            }
    
        }

Model

        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));
                }
            }
        }

ViewModel

        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
        }

Converter

        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;
            }
        }

   
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top