How to do multiple items Data binding in WPF - c#

I have a particular scenarios. My application looks like this.
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.

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.

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

Related

How to use Static Resource for WPF datagrid comboBox binding

I am developing WPF application with entity framework as well. But I DO NOT use MVVM I have a ENUM types, So I need to initialize the combo box item source with ALL enum types and select the value based on my Data. to simplify just consider binding a simple list to combo box. I have tired different ways but there is a problem I cant figure out.
<Page x:Class="Library.View.Reader"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Library.View"
mc:Ignorable="d"
d:DesignHeight="300"
Title="Reader" Width="900">
<Grid Margin="0,0,0,0">
<DataGrid Name="grid_reader" AutoGenerateColumns="True" HorizontalAlignment="Left" Height="126" Margin="23,20,0,0" VerticalAlignment="Top" Width="845" RowEditEnding="grid_reader_RowEditEnding" AutoGeneratingColumn="grid_reader_AutoGeneratingColumn">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Type"
ItemsSource="{DynamicResource enumlist}}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValueBinding="{Binding Type}"
</DataGrid.Columns>
</DataGrid>
</Grid>
I have tried DynamicResource ,StaticResource,Binding. None of them works!
public partial class Reader : Page
{
public Reader() // Redaer is my page in xaml
{
LibraryDataAccess.Model1 model = new Model1();
List<LibraryDataAccess.Model.Reader> list = new List<LibraryDataAccess.Model.Reader>();
list = model.Readers.ToList();
public ObservableCollection<ReaderType> enumlist { get; set; }
// initialize datagrid succefully Also enumlist = getEnumValues();
enumlist = new ObservableCollection<ReaderType>();
//enumlist = new List<LibraryDataAccess.EnumTypes.ReaderType>();
typelist = Enum.GetValues(typeof
(LibraryDataAccess.EnumTypes.ReaderType))
.Cast<LibraryDataAccess.EnumTypes.ReaderType>().Select(x => new ReaderType { Id = (int)x, Name = x.ToString() }).ToList();
foreach (var item in typelist)
{
enumlist.Add(item);
}
grid_reader.ItemsSource = list;
}
public class ReaderType
{
public int Id { get; set; }
public string Name { get; set; }
}
}
nothing is loaded into the Combo. What is the solution. Thanks
EDITED:
I am 99% sure that the problem is ItemSource of combo BUT:
I need the combo filled with enum values and the selected value is shown as given, suhc as Staff (which is in enum list with id 2) anyway the combo would not be filled at all. I am using this in a separate wpf Page.
I think the problem is in my data context related to combo box, i tried with sparated combo box even, with the binding mentioned above, but it does not work.
When I use AUTOGENERATED = true, the combo box would be created nicely with seleted value.
if i have undertant what you want (else i 'll destroy the answer): here is a solution using the datagrid with combobox using enumlist: i am using markupextension for that
your class.cs: i have implemented INotifyPropertyChanged if you want update data
using System.ComponentModel;
namespace zzWpfApp1
{
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum ReaderType
{
[Description("Super Chief")] Chief,
[Description("Super Staff")] Staff,
[Description("super Officer")] Officer,
}
public class User : INotifyPropertyChanged
{
private string _name;
private ReaderType _readerType;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
public ReaderType ReaderType
{
get { return _readerType; }
set
{
_readerType = value;
NotifyPropertyChanged("ReaderType");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
EnumConverter.cs: generic file to trap the description of enum file
using System;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Markup;
namespace zzWpfApp1
{
public class EnumBindingSourceExtension : MarkupExtension
{
private Type _enumType;
public Type EnumType
{
get { return this._enumType; }
set
{
if (value != this._enumType)
{
if (null != value)
{
Type enumType = Nullable.GetUnderlyingType(value) ?? value;
if (!enumType.IsEnum)
throw new ArgumentException("Type must be for an Enum.");
}
this._enumType = value;
}
}
}
public EnumBindingSourceExtension(Type enumType)
{
this.EnumType = enumType;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (null == this._enumType)
throw new InvalidOperationException("The EnumType must be specified.");
Type actualEnumType = Nullable.GetUnderlyingType(this._enumType) ?? this._enumType;
Array enumValues = Enum.GetValues(actualEnumType);
if (actualEnumType == this._enumType)
return enumValues;
Array tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1);
enumValues.CopyTo(tempArray, 1);
return tempArray;
}
}
public class EnumDescriptionTypeConverter : EnumConverter
{
public EnumDescriptionTypeConverter(Type type)
: base(type)
{
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture,
object value, Type destinationType)
{
if (destinationType == typeof(string))
{
if (value != null)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
if (fi != null)
{
var attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description)))
? attributes[0].Description
: value.ToString();
}
}
return string.Empty;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
---
mainwindow.xaml.cs:
namespace zzWpfApp1
{
public partial class MainWindow : Window
{
public ObservableCollection<User> Users { get; set; }
public MainWindow()
{
//Sample of different users
List<User> users = new List<User>();
users.Add(new User() { Name = "Donald Duck", ReaderType = ReaderType.Chief });
users.Add(new User() { Name = "Mimmi Mouse", ReaderType = ReaderType.Staff });
users.Add(new User() { Name = "Goofy", ReaderType = ReaderType.Officer });
Users = new ObservableCollection<User>(users);
InitializeComponent();
DataContext = this;
}
}
}
xaml file:
<DataGrid Name="grid_reader" AutoGenerateColumns="False" Margin="20,20,300,20" ItemsSource="{Binding Users}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridComboBoxColumn Header="ReaderType" MinWidth="150"
SelectedItemBinding="{Binding ReaderType}"
ItemsSource="{Binding Source={local:EnumBindingSource {x:Type local:ReaderType}}}"/>
</DataGrid.Columns>
Finally I found a better and simpler answer for my question, let me share with you guys:
We need to define a class as the dataSource of our combo box. So all of combo boxes must have these datas, and then based on a property which we set(from the data grids data source) combo box value must be selected and shown. Also we need to define a combo box resource at the top of the page, or window.
<Page.Resources>
<local:viewmodel x:Key="viewmodel"/>
</Page.Resources>
<Grid Margin="0,0,0,0">
<DataGrid Name="grid_doc" AutoGenerateColumns="True" HorizontalAlignment="Left" Height="100" Margin="31,55,0,0" VerticalAlignment="Top" Width="636">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="PublisherId"
ItemsSource="{StaticResource viewmodel}"
SelectedValueBinding="{Binding PublisherId , UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Value"
SelectedValuePath="Key">
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
<ComboBox HorizontalAlignment="Left" Margin="130,193,0,0" VerticalAlignment="Top" Width="120"
ItemsSource="{StaticResource viewmodel}"
SelectedValue="{Binding PublisherId , UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Value"
SelectedValuePath="Key">
</ComboBox>
</Grid>
NOTE here, I used Key and Value for selected and display members. its important, even key and value are wrong. it MUST be Value and Key, with uppercase.
I posted a simpler version, not enum members, it does not make any difference. you can create your list of enum members, and then add it into in your viewmodel class.It doesn't matter if your viewmodel class has other properties. just be sure to return an Enumerable data or inherit from a upper Enumerable class, as I did.
public partial class Document : Page
{
LibraryDataAccess.Model1 model;
List<LibraryDataAccess.Model.Document> list;
public Document()
{
model = new Model1();
list = new List<LibraryDataAccess.Model.Document>();
list = model.Documents.ToList();
InitializeComponent();
list.Add(new LibraryDataAccess.Model.Document { Id = 1, PublisherId = 2, Title = "sdfs" });
grid_doc.ItemsSource = list;
}
public class viewmodel : List<KeyValuePair<string,string>>
{
public viewmodel()
{
this.Add(new KeyValuePair<string, string>(1.ToString(), "s"));
this.Add(new KeyValuePair<string, string>(2.ToString(), "t"));
}
}
Thanks to previous answer and to these two links which helped me :
binding
static resource

wpf - One ViewModel that interacts with multiple instances of a Model

I have a WorkspaceViewModel that handles addition and deletion of tab items dynamically through an ObservableCollection. Each time a tab is connected to a PayslipModel, all bindings work fine but one problem I am having is that;
I have a save button in the UserControl who's DataContext is set to WorkspaceViewModel and I would like to save whatever info is being displayed in the selected tab. Now, each time a tab is added, a new instance of PayslipModel is created, which is exactly what I want because I don't want bindings to be shared for all tabs. However, I am unable to save what is being displayed since PayslipModel has multiple instances, therefore nothing is returned (temporarily using MessageBox to test if info is being retrieved) when I hit save.
I created a diagram to better explain my situation:
Is it possible to access the current instance when a tab is selected or cycle through all instances and do something like batch saving?
This is a working example which shows one of the possiblities:
View
<TabControl DataContext="{Binding}" ItemsSource="{Binding Models}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" >
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<DockPanel>
<Button DockPanel.Dock="Top" Content="Click Me" Command="{Binding DataContext.PCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabControl}}"
CommandParameter="{Binding Desc}"/>
<TextBlock Text="{Binding Desc}" >
</TextBlock>
</DockPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Model View
public class ModelView
{
public ModelView()
{
_models = new ObservableCollection<Model>();
_pCommand = new Command(DoParameterisedCommand);
}
ObservableCollection<Model> _models;
public ObservableCollection<Model> Models { get { return _models; } }
private void DoParameterisedCommand(object parameter)
{
MessageBox.Show("Parameterised Command; Parameter is '" +
parameter.ToString() + "'.");
}
Command _pCommand;
public Command PCommand
{
get { return _pCommand; }
}
}
Model
public class Model : INotifyPropertyChanged
{
string _desc;
public string Desc { get { return _desc; } set { _desc = value; RaisePropertyChanged("Desc"); } }
string _name;
public string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
Command
public class Command : ICommand
{
public Command(Action<object> parameterizedAction, bool canExecute = true)
{
_parameterizedAction = parameterizedAction;
_canExecute = canExecute;
}
Action<object> _parameterizedAction = null;
bool _canExecute = false;
public bool CanExecute
{
get { return _canExecute; }
set
{
if (_canExecute != value)
{
_canExecute = value;
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}
public event EventHandler CanExecuteChanged;
bool ICommand.CanExecute(object parameter)
{
return _canExecute;
}
void ICommand.Execute(object parameter)
{
this.DoExecute(parameter);
}
public virtual void DoExecute(object param)
{ if (_parameterizedAction != null)
_parameterizedAction(param);
else
throw new Exception();
}
}
Use this to initialize:
public MainWindow()
{
InitializeComponent();
ModelView mv = new ModelView();
mv.Models.Add(new Model() { Name = "a", Desc = "aaa" });
mv.Models.Add(new Model() { Name = "b" , Desc = "bbb"});
mv.Models.Add(new Model() { Name = "c", Desc = "cccc" });
this.DataContext = mv;
}

ComboBox, is it possible to have ItemsSource and SelectedValue bound to different sources?

I have two different objects that are pointing at each other. The first object represents a division in a company. That object has two collection: Employees, which is all the employees working in the division and Project, which is all the special projects that are in progress within that division. So the first object looks like this:
public class Division : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
ObservableCollection<Employee> _employees;
ObservableCollection<Project> _projects;
public Division()
{
Employees = new ObservableCollection<Employee>();
Projects = new ObservableCollection<Project>();
}
public ObservableCollection<Employee> Employees
{
get { return _employees; }
set
{
if (_employees != value)
{
_employees = value;
PropertyChanged(this, new PropertyChangedEventArgs("Employees"));
}
}
}
public ObservableCollection<Project> Projects
{
get { return _projects; }
set
{
if (_projects != value)
{
_projects = value;
PropertyChanged(this, new PropertyChangedEventArgs("Projects"));
}
}
}
public void AddNewProject()
{
this.Projects.Add(new Project(this));
}
}
Notice that when adding a new project to the division, I pass a reference to the division into that project, which looks like this:
public class Project : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
string _projectName;
DateTime _deadline = DateTime.Now;
Division _division;
ObservableCollection<Employee> _members;
public Project()
{
Members = new ObservableCollection<Employee>();
}
public Project(Division div)
{
Members = new ObservableCollection<Employee>();
Division = div;
}
public string ProjectName
{
get { return _projectName; }
set
{
if (_projectName != value)
{
_projectName = value;
PropertyChanged(this, new PropertyChangedEventArgs("ProjectName"));
}
}
}
public DateTime Deadline
{
get { return _deadline; }
set
{
if (_deadline != value)
{
_deadline = value;
PropertyChanged(this, new PropertyChangedEventArgs("Deadline"));
}
}
}
public Division Division
{
get { return _division; }
set
{
if (_division != value)
{
if (_division != null)
{
_division.Employees.CollectionChanged -= members_CollectionChanged;
}
_division = value;
if (_division != null)
{
_division.Employees.CollectionChanged += members_CollectionChanged;
}
PropertyChanged(this, new PropertyChangedEventArgs("Division"));
}
}
}
public ObservableCollection<Employee> Members
{
get { return _members; }
set
{
if (_members != value)
{
if (_members != null)
{
_members.CollectionChanged -= members_CollectionChanged;
}
_members = value;
if (_members != null)
{
_members.CollectionChanged += members_CollectionChanged;
}
PropertyChanged(this, new PropertyChangedEventArgs("Members"));
}
}
}
public ObservableCollection<Employee> AvailableEmployees
{
get
{
if (Division != null){
IEnumerable<Employee> availables =
from s in Division.Employees
where !Members.Contains(s)
select s;
return new ObservableCollection<Employee>(availables);
}
return new ObservableCollection<Employee>();
}
}
void members_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
PropertyChanged(this, new PropertyChangedEventArgs("AvailableEmployees"));
}
}
The reason I'm doing it like this is, that the project could have any type of team working on it, but only from within the division. So, when building a dashboard for the division, the manager could select any of the employees to that project but without putting in an employee that is already assigned to it. So, the AvailableEmployees property in the project object always keeps track of who is not already assigned to that project.
The problem I'm having is how to translate this into a UI. The experiment I've done so far looks like this:
<UserControl x:Class="Test.Views.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:local="clr-namespace:Test.Views"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<ListBox ItemsSource="{Binding Div.Projects}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="Transparent"
BorderThickness="0, 0, 0, 2"
BorderBrush="Black"
Margin="0, 0, 0, 5"
Padding="0, 0, 0, 5">
<StackPanel>
<TextBox Text="{Binding ProjectName}"/>
<ListBox ItemsSource="{Binding Members}">
<ListBox.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:TestView}, Path=DataContext.AvailableEmployees}"
DisplayMemberPath="FirstName"
Text="{Binding FirstName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Add Employee to Project"
Command="{Binding RelativeSource={RelativeSource AncestorType=local:TestView}, Path=DataContext.AddEmployeeToProject}"
CommandParameter="{Binding}"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Add New Project"
Command="{Binding AddNewProject}" />
</StackPanel>
The view model associated with this view is as follows:
public class TestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private Division _div;
public TestViewModel(Division div)
{
Div = div;
AddNewProject = new DelegateCommand(OnAddNewProject);
AddEmployeeToProject = new DelegateCommand<Project>(OnAddEmployeeToProject);
}
public DelegateCommand AddNewProject { get; set; }
public DelegateCommand<Project> AddEmployeeToProject { get; set; }
public Division Div
{
get { return _div; }
set
{
if (_div != value)
{
_div = value;
PropertyChanged(this, new PropertyChangedEventArgs("Div"));
}
}
}
private void OnAddNewProject()
{
Div.AddNewProject();
}
private void OnAddEmployeeToProject(Project proj)
{
var availables = proj.AvailableEmployees;
if (availables.Count > 0)
{
proj.Members.Add(availables[0]);
}
}
}
However, I cannot get the combobox for each employee in each project to work. It seems like the selected item/value is bound to the itemssource, and each time the combobox turns out blank. I've tried to do this also with SelectedValue and SelectedItem properties for the combobox, but none worked.
How do I get these two separated. Is there anything else I'm missing here?
OK. After so many experiments the best solution I came up with was to create my own user control that is composed of both a button and a combobox that imitate the behavior I was expecting of the combobox on it own.
First, I had a really stupid mistake in the model where both lists of members Project and Division contain the same instances of Employee, which makes the AvailableEmployees property buggy. What I really needed to do is to create a list of copies of employees in the Project instead of just references.
In any case, I created a new user control and called it DynamicSourceComboBox. The XAML of this control looks like this:
<Grid>
<Button x:Name="selected"
Content="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=SelectedValue}"
Click="selected_Click"/>
<ComboBox x:Name="selections"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=ItemsSource}"
DisplayMemberPath="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=DisplayMemberPath}"
Visibility="Collapsed"
SelectionChanged="selections_SelectionChanged"
MouseLeave="selections_MouseLeave"/>
</Grid>
I have here a few bindings from the button and the combobox to properties in my user control. These are actually dependency properties. The code-behind of my user control looks like this:
public partial class DynamicSourceComboBox : UserControl
{
public DynamicSourceComboBox()
{
InitializeComponent();
}
public object SelectedValue
{
get { return (object)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(object), typeof(DynamicSourceComboBox), new PropertyMetadata(null));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
ComboBox.ItemsSourceProperty.AddOwner(typeof(DynamicSourceComboBox));
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
public static readonly DependencyProperty DisplayMemberPathProperty =
ComboBox.DisplayMemberPathProperty.AddOwner(typeof(DynamicSourceComboBox));
private void selected_Click(object sender, RoutedEventArgs e)
{
selected.Visibility = Visibility.Hidden;
selections.Visibility = Visibility.Visible;
selections.IsDropDownOpen = true;
}
private void selections_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
selections.Visibility = Visibility.Collapsed;
selected.Visibility = Visibility.Visible;
selections.IsDropDownOpen = false;
if (e.AddedItems.Count == 1)
{
var item = e.AddedItems[0];
Type itemType = item.GetType();
var itemTypeProps = itemType.GetProperties();
var realValue = (from prop in itemTypeProps
where prop.Name == DisplayMemberPath
select prop.GetValue(selections.SelectedValue)).First();
SelectedValue = realValue;
}
}
private void selections_MouseLeave(object sender, MouseEventArgs e)
{
selections.Visibility = Visibility.Collapsed;
selected.Visibility = Visibility.Visible;
selections.IsDropDownOpen = false;
}
}
These dependency properties imitate the properties with similar names in ComboBox but they are hooked up to the internal combobox and the button in a way that makes them behave together as a single complex combobox.
The Click event in the button hides it and present the combobox to make the effect of just a box that is opening. Then I have a SelectionChanged event in the combobox firing to update all the needed information and a MouseLeave event just in case the user doesn't make any real selection change.
When I need to use the new user control, I set it up like this:
<local:DynamicSourceComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorLevel=1, AncestorType=ListBox}, Path=DataContext.AvailableEmployees}"
DisplayMemberPath="FirstName"
SelectedValue="{Binding FirstName, Mode=TwoWay}"/>
Of course, for all of it to work, I have to make a lot of hookups with PropertyChanged events in the models, so the Projects instance will know to raise a PropertyChanged event for AvailableEmployees any time a change is made, but this is not really the concern of this user control itself.
This is a pretty clunky solution, with a lot of extra code that is a bit hard to follow, but it's really the best (actually only) solution I could have come up with to the problem I had.

WPF Converter JSON string to multiple textboxes

I've already made a workaround for this problem because of time constraints at work, although I still want to ask for learning purposes.
So I had this issue where I was making an editor screen for some record data, and in this record was a field called 'Quantity'. However, when designed, it was made a quantity placeholder, but it meant different things. So to explain, it is a SkuReference table, that has a 'Type' that defines if it's a 'Quantity per Pack', 'Roll Length', or 'CBC'. Well, for 'Quantity per Pack' and 'Roll Length', a simple number works, however for the 'CBC' (meaning, Corners/Borders/Centers) the data is stored as a JSON string object:
{ 'Corners': 10, 'Borders': 20, 'Centers': 30 }
Now on the WPF screen, if the data is identified as a 'CBC', I route the data to three textboxes, all bound to the 'Quantity' property of the parent object and using a converter and parameters to identify each one and I put the appropriate value into each textbox. Works fine.
The problem I have is when trying to work the ConvertBack part of the converter. I realized that I do not have reference to the original string property that I can edit and supply the new value to, or access to the other textboxes to just rebuild a new string to return. I was trying to come up with a resolution maybe using MultiBinding in my head, but could not completely come through with an answer.
Is this even possible? BTW I ended up just creating new properties that were split up and when the parent object was set parsed and passed around data. However, for future reference it would seem cleaner to me to just use the original data and a converter without the extra work.
Below is other code for reference:
XAML, UpsertSkuReference.Quantity is the JSON string above
<StackPanel Grid.Column="5">
<TextBlock Text="CBC" />
<StackPanel Orientation="Horizontal">
<TextBox Width="30" Text="{Binding UpsertSkuReference.Quantity, ConverterParameter=co, Converter={StaticResource CBCToIndividualConverter}}" IsEnabled="{Binding CBCIsChecked}" />
<TextBox Width="30" Margin="5,0,0,0" Text="{Binding UpsertSkuReference.Quantity, ConverterParameter=b, Converter={StaticResource CBCToIndividualConverter}}" IsEnabled="{Binding CBCIsChecked}" />
<TextBox Width="30" Margin="5,0,0,0" Text="{Binding UpsertSkuReference.Quantity, ConverterParameter=ce, Converter={StaticResource CBCToIndividualConverter}}" IsEnabled="{Binding CBCIsChecked}" />
</StackPanel>
</StackPanel>
Converter
public class CBCToIndividualConverter : IValueConverter
{
JavaScriptSerializer json = new JavaScriptSerializer();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//--value = CBC JSON object string
//--parameter = [co]: Corners, [b]: Borders, [ce]: Centers
if (value != null)
{
if (parameter == null) { throw new Exception("CBCToIndividualConverter: parameter cannot be null"); }
if (new string[] { "co", "b", "ce" }.Contains(parameter.ToString().ToLower()) == false)
{ throw new Exception("CBCToIndividualConverter: parameter must be 'co' for Corners, 'b' for Borders, or 'ce' for Centers"); }
CornerBorderCenterModel cbc = json.Deserialize<CornerBorderCenterModel>(value.ToString());
switch (parameter.ToString().ToLower())
{
case "co": { return cbc.Corners; }
case "b": { return cbc.Borders; }
case "ce": { return cbc.Centers; }
default: { return null; }
}
}
else { return null; }
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
//--value = number for parameter type
//--parameter = [co]: Corners, [b]: Borders, [ce]: Centers
//--?? Uh Oh
}
return null;
}
}
Converters are not supposed to be used like that. There are cleaner ways. I would suggest you a slight bigger refactoring:
Create 3 properties: Borders, Corners and Centers, on the class that contains the string Quantity (SkuReference?);
When you set any of them, update the Quantity; when you update the Quantity, you try to parse in a CornerBorderCenterModel instance and then update the 3 properties with the values of this instance. All this work is doing implementing the OnPropertyChanged method (see my code later);
In the view, bind every TextBox just with the relative property. This way, you need no converter at all (this works if the external DataContext is the instance of UpsertSkuReference; otherwise, you have to set the DataContext of the StackPanel this way: DataContext="{Binding UpsertSkuReference}", so the bindings of the 3 TextBoxes can find the properties on the object.
The whole round:
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
namespace XXX
{
public class CornerBorderCenterModel
{
public int Corners { get; set; }
public int Borders { get; set; }
public int Centers { get; set; }
}
public class SkuReference : INotifyPropertyChanged
{
static JavaScriptSerializer json = new JavaScriptSerializer();
private static string[] _jsonStringParts =
new[] { nameof(Borders), nameof(Corners), nameof(Centers) };
public SkuReference()
{
PropertyChanged += OnPropertyChanged;
}
public int Corners
{
get { return _Corners; }
set
{
if (_Corners != value)
{
_Corners = value;
RaisePropertyChanged();
}
}
}
private int _Corners;
public int Borders
{
get { return _Borders; }
set
{
if (_Borders != value)
{
_Borders = value;
RaisePropertyChanged();
}
}
}
private int _Borders;
public int Centers
{
get { return _Centers; }
set
{
if (_Centers != value)
{
_Centers = value;
RaisePropertyChanged();
}
}
}
private int _Centers;
private void UpdateCBCFromQuantity()
{
//if Quantity is a CBC and is not null do the following:
var cbc = json.Deserialize<CornerBorderCenterModel>(_Quantity.ToString());
if (cbc != null)
{
Corners = cbc.Corners;
Borders = cbc.Borders;
Centers = cbc.Centers;
}
}
public string Quantity
{
get { return _Quantity; }
set
{
if (_Quantity != value)
{
_Quantity = value;
RaisePropertyChanged();
}
}
}
private string _Quantity;
private void UpdateJsonStringFromCBC()
{
Quantity = string.Format(
"{{ 'Corners': {0}, 'Borders': {1}, 'Centers': {2} }}",
_Corners,
_Borders,
_Centers);
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_jsonStringParts.Contains(e.PropertyName))
UpdateJsonStringFromCBC();
else if (e.PropertyName == nameof(Quantity))
UpdateCBCFromQuantity();
}
}
}
<StackPanel Orientation="Horizontal" DataContext="{Binding UpsertSkuReference}">
<TextBox Text="{Binding Corners}" IsEnabled="{Binding CBCIsChecked}" />
<TextBox Text="{Binding Borders}" IsEnabled="{Binding CBCIsChecked}" />
<TextBox Text="{Binding Centers}" IsEnabled="{Binding CBCIsChecked}" />
</StackPanel>
Note that Quantity, Borders, Corners and Centers must raise notifications when they changed (just as I made, or using more advanced tools like the Reactive library), otherwise the whole round can't work.

MVVM Light Binding to Observable Collection

I am using MVVM light in conjunction with EF4 and SQL CE 4, but I am having issues with my observable collection. My application doesn't neccessarily need to use the mvvm pattern, but since I need the benefits of an observablecollection I have decided to learn how to integrate it. I can successfully link my database of property entitites to my listbox and display them, I can also link some properties of these entities to textboxes, but where I am stuck is when I try to update these properties by typing in the textbox. Here is my xaml code for a textbox and the listbox:
<TextBox Text="{Binding SaleTitle, ValidatesOnDataErrors=true, Mode=TwoWay}"
<ListBox Height="424"
Margin="24,80,0,0"
x:Name="listBoxProperties"
VerticalAlignment="Top"
ItemTemplate="{StaticResource propertySummaryTemplate}"
IsSynchronizedWithCurrentItem="True"
Width="216" BorderThickness="0" Background="{x:Null}"
FontFamily="Segoe UI"
ItemsSource="{Binding PropertyList}"
SelectedItem="{Binding CurrentProperty, Mode=TwoWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
UseLayoutRounding="True"
HorizontalAlignment="Left"
ScrollViewer.VerticalScrollBarVisibility="Disabled" >
</ListBox>
Here is the code of part of my MainViewModel:
private string _SaleTitle;
public string SaleTitle
{
get
{
if (CurrentProperty != null)
return CurrentProperty.SaleTitle;
else
return "";
}
set
{
_SaleTitle = value;
RaisePropertyChanged("SaleTitle");
}
}
private RelayCommand loadCommand;
public ICommand LoadCommand
{
get
{
if (loadCommand == null)
loadCommand = new RelayCommand(() => Load());
return loadCommand;
}
}
private void Load()
{
PropertyList = new ObservableCollection<Property>((from property in entities.Properties.Include("Images")
select property));
propertyView = CollectionViewSource.GetDefaultView(PropertyList);
if (propertyView != null)
propertyView.CurrentChanged += new System.EventHandler(propertyView_CurrentChanged);
RaisePropertyChanged("CurrentContact");
RaisePropertyChanged("SaleTitle");
RaisePropertyChanged("Address");
RaisePropertyChanged("AuctioneerName");
RaisePropertyChanged("AgentName");
RaisePropertyChanged("Price");
RaisePropertyChanged("NextBid");
RaisePropertyChanged("Status");
}
void propertyView_CurrentChanged(object sender, System.EventArgs e)
{
RaisePropertyChanged("CurrentContact");
RaisePropertyChanged("SaleTitle");
RaisePropertyChanged("Address");
RaisePropertyChanged("AuctioneerName");
RaisePropertyChanged("AgentName");
RaisePropertyChanged("Price");
RaisePropertyChanged("NextBid");
RaisePropertyChanged("Status");
}
private Property _CurrentProperty;
public Property CurrentProperty
{
get
{
if (propertyView != null)
return propertyView.CurrentItem as Property;
return null;
}
set
{
_CurrentProperty = value;
RaisePropertyChanged("CurrentProperty");
}
}
public ObservableCollection<Property> PropertyList
{
get
{
return propertyList;
}
set
{
if (propertyList == value)
{
return;
}
var oldValue = propertyList;
propertyList = value;
// Update bindings, no broadcast
RaisePropertyChanged(PropertiesPropertyName);
}
}
public MainViewModel()
{
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
}
else
{
// Code runs "for real"
entities = new Model1Container1();
}
}
////public override void Cleanup()
////{
//// // Clean up if needed
//// base.Cleanup();
////}
}
}
The listbox is populated successfully with the content from current selected item, but when I type in it and click out of it or do anything to lose focus it simply goes back to what was there before.
Take a look at your SaleTitle property definition. It Reads value from CurrentProperty.Saletitle but sets value to local field which is not used anythere.

Categories