WPF Master-Details view with Listbox and Combobox with Binding - wpf

I've been looking around for an answer to my question for a few days now, but am not able to find a solution.
The problem is that the combobox updates the Test object in User class with the the previous selected Users'.
i.e. you select user2 and user2 has test2, you then select user5 that has test5. Now if you select user2 again, it will show that it has test5.
Here is some code. I have two classes Users and Tests. And two ObservableCollections for each of those. This is how I've got them setup:
public class User
{
public string Name { get; set; }
public int test { get; set; }
public test userTest { get; set; }
}
public class test
{
public int ID { get; set; }
public String Name { get; set; }
}
public class ListOfTests:ObservableCollection<test>
{
public ListOfTests()
{
for (int i = 0; i < 4; i++)
{
test newTest = new test();
newTest.ID = i;
newTest.Name = "Test " + i;
Add(newTest);
}
}
}
public class ListOfUsers: ObservableCollection<User>
{
public ListOfUsers()
{
ListOfTests testlist = new ListOfTests();
for (int i = 0; i < 10; i++)
{
User newUser = new User();
newUser.Name = "User " + i;
newUser.ID = i;
newUser.userTest = testlist[i];
Add(newUser);
}
}
}
And the XAML is:
<Window x:Class="ComboboxTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ComboboxTest"
Title="Window1" Height="300" Width="300">
<StackPanel x:Name="SP1">
<StackPanel.Resources>
<local:ListOfTests x:Key="ListOfTests" />
</StackPanel.Resources>
<ListBox ItemsSource="{Binding}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True"/>
<TextBox Text="{Binding Path=Name}" Foreground="Black" />
<TextBox Text="{Binding Path=userTest}" />
<ComboBox SelectedItem="{Binding Path=userTest}"
SelectedValue="{Binding Path=userTest.ID}"
ItemsSource="{Binding Source={StaticResource ListOfTests}}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
Foreground="Black" />
</StackPanel>
Now if I change the Binding on the SelectedItem to "{Binding Path=userTest, Mode=OneWay}" then it works, but i can not change the it manually.
Here is a kicker thought... If I target .Net 4.0 (VS2010) then it works fine...
Can anyone please help me find a solution to this?

If I'm understanding your question, it sounds like that WPF isn't being notified when the value of a property changes. You can get around this by implementing the INotifyPropertyChanged interface. For example, the User class would look something like this:
public class User : INotifyPropertyChanged
{
private string name = string.Empty;
public string Name
{
get { return this.name; }
set
{
this.name = value;
this.OnPropertyChanged("Name");
}
}
private int test = 0;
public int Test
{
get { return this.test; }
set
{
this.test = value;
this.OnPropertyChanged("Test");
}
}
private test userTest = null;
public test UserTest
{
get { return this.userTest; }
set
{
this.userTest = value;
this.OnPropertyChanged("UserTest");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler eh = this.PropertyChangd;
if(null != eh)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
You should probably do the same for your test class, as well.
WPF will watch for when the PropertyChanged event is fired, and updates any affected bindings as needed. This should cause the selected item in the ComboBox to change back to the test for user2.
Update: OK, I think I got this working. I think that you're missing part of the code in what you posted (like what the DataContext for the Window is), but here's what I got working:
I created a class called ViewModel, which is set to the DataContext of the main Window. Here's its code:
class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
for(int i = 0; i < 4; i++)
{
this.tests.Add(new Test()
{
ID = i,
Name = "Test " + i.ToString(),
});
}
for(int i = 0; i < 4; i++)
{
this.users.Add(new User()
{
Name = "User " + i.ToString(),
ID = i,
UserTest = this.tests[i],
});
}
}
private ObservableCollection<User> users = new ObservableCollection<User>();
public IEnumerable<User> Users
{
get { return this.users; }
}
private ObservableCollection<Test> tests = new ObservableCollection<Test>();
public IEnumerable<Test> Tests
{
get { return this.tests; }
}
private User currentUser = null;
public User CurrentUser
{
get { return this.currentUser; }
set
{
this.currentUser = value;
this.OnPropertyChanged("CurrentUser");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
var eh = this.PropertyChanged;
if(null != eh)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
I moved the creation of the two lists to code. One thing I noticed in your sample is that one instance of ListOfTests was used as the ItemsSource of the ComboBox, while another instance was used to build ListOfUsers. I'm not sure if that was part of the problem or not, but it is better to just have one list of tests.
The XAML for the main Window is the following:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ListBox ItemsSource="{Binding Path=Users}"
SelectedItem="{Binding Path=CurrentUser}"
DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True">
</ListBox>
<TextBox Text="{Binding Path=CurrentUser.Name}" />
<TextBox Text="{Binding Path=CurrentUser.UserTest.Name}" />
<ComboBox ItemsSource="{Binding Path=Tests}"
SelectedItem="{Binding Path=CurrentUser.UserTest}"
DisplayMemberPath="Name" />
</StackPanel>
</Window>
The key to getting things working is the CurrentUser property. It is bound to ListBox.SelectedItem, and ComboBox.SelectedItem is bound to CurrentUser.UserTest. This will change the selection in the ComboBox to represent the test of the user selected in the ListBox.
I got this all working using Visual Studio 2008 SP1, so hopefully it will work for you as well. If you have any problems getting this working, let me know and I'll see what I can do.

Andy,
Here is a more readable extract from the code I have now.
public class User : INotifyPropertyChanged
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
private int iD;
public int ID
{
get
{
return iD;
}
set
{
iD = value;
OnPropertyChanged("ID");
}
}
private test userTest;
public test UserTest
{
get
{
return userTest;
}
set
{
userTest = value;
OnPropertyChanged("UserTest");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler eh = this.PropertyChanged;
if (null != eh)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
Looks better than in the comments.
Regards
Corne

Related

wpf ListItem SelectedValue Object is always null

I have a simple ListBox and a TextBox as under.I want to display the selectedvalue property of Listbox in the textbox,but my ViewModel's selected object is always null.
What am i missing here?
My XAML
<StackPanel>
<Canvas>
<TextBox x:Name="TxtMail" Width="244" FontSize="14" Canvas.Left="36" Canvas.Top="34" Height="20" Text="{Binding CurrentRec.Name,Mode=OneWay}" />
<ListBox x:Name="AllMatching" Width="{Binding ElementName=TxtMail,Path=Width}" Height="100" Canvas.Top="54" Canvas.Left="36" DisplayMemberPath="Name" SelectedItem="{Binding CurrentRec,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedValue="Name" SelectedValuePath="Name" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" />
<Button Content="Test" x:Name="cmdtest" Click="cmdtest_Click"/>
</Canvas>
My ViewModel:
public class VM_Data : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public List<DM_Data> AllData;
public DM_Data CurrentRec;
public VM_Data()
{
LoadData();
}
public int ID
{
get { return p_ID; }
set
{
if (p_ID != value)
{
RaisePropertyChangedEvent("ID");
p_ID = value;
}
}
}
public double SP
{
get { return p_SP; }
set
{
if (p_SP != value)
{
RaisePropertyChangedEvent("SP");
p_SP = value;
}
}
}
public double CP
{
get { return p_CP; }
set
{
if (p_CP != value)
{
RaisePropertyChangedEvent("CP");
p_CP = value;
}
}
}
public string Name
{
get { return p_Name; }
set
{
if (p_Name != value)
{
RaisePropertyChangedEvent("Name");
p_Name = value;
}
}
}
private void LoadData()
{
AllData = new List<DM_Data>();
string[] strNames = "Jatinder;Shashvat;shashikala;shamsher;shahid;justin;jatin;jolly;ajay;ahan;vijay;suresh;namita;nisha;negar;zenith;zan;zen;zutshi;harish;hercules;harman;ramesh;shashank;mandeep;aman;amandeep;amarjit;asim;akshay;amol;ritesh;ritivik;riz;samana;samaira;bhagwandass;bhagwan;bhawna;bhavna".Split(';');
for(int i=0;i<=strNames.GetUpperBound(0);i++)
{
DM_Data NewRec = new DM_Data();
NewRec.CP = new Random().Next(200, 400);
NewRec.SP = new Random().Next(1, 10);
NewRec.ID = i + 1;
NewRec.Name = strNames[i];
AllData.Add(NewRec);
}
AllData = AllData.OrderBy(item => item.Name).ToList();
}
private void RaisePropertyChangedEvent(string Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
}
}
My DataModel
public class DM_Data
{
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public int ID
{
get { return p_ID; }
set { p_ID = value; }
}
public double SP
{
get { return p_SP; }
set { p_SP = value; }
}
public double CP
{
get { return p_CP; }
set { p_CP = value; }
}
public string Name
{
get { return p_Name; }
set { p_Name = value; }
}
MainWindow.Xaml.cs
public partial class MainWindow : Window
{
VM_Data ViewModel;
public MainWindow()
{
InitializeComponent();
ViewModel = new VM_Data();
this.DataContext = ViewModel;
AllMatching.ItemsSource = ViewModel.AllData;
}
private void cmdtest_Click(object sender, RoutedEventArgs e)
{
DM_Data crec = ViewModel.CurrentRec;
}
}
CurrentRec must be a property that raises the PropertyChanged event:
private DM_Data _currentRec;
public DM_Data CurrentRec
{
get { return _currentRec; }
set { _currentRec = value; RaisePropertyChangedEvent("CurrentRec"); }
}
In the code you have posted, it is a field and you cannot bind to fields:
public DM_Data CurrentRec;
You can't bind to fields! CurrentRec must be a property. At now it is a field.
Why do you set ItemsSource in code-behind? Set it in XAML.
You should call RaisePropertyChangedEvent after you've changed backing field, not before.
It is not right pattern for events raising: if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(Property)); } You need to save event delegate to variable first or use ?.Invoke.
Don't create new instances of Random on every iteration of loop because you will get equal values. Create the only one outside of the loop and use it.
What you are doing is kind of MVVM, but not really.
Here is a quick fix anyway:
Please take a look at the Bindings.
<StackPanel>
<Canvas>
<TextBox x:Name="TxtMail" Width="244" FontSize="14" Canvas.Left="36" Canvas.Top="34" Height="20" Text="{Binding ElementName=AllMatching, Path=SelectedItem.Name}" />
<ListBox x:Name="AllMatching"
Width="{Binding ElementName=TxtMail,Path=Width}"
Height="100"
Canvas.Top="54"
Canvas.Left="36"
DisplayMemberPath="Name"
SelectedItem="{Binding CurrentRec,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto" />
<Button Content="Test" x:Name="cmdtest" Click="cmdtest_Click"/>
</Canvas>
</StackPanel>
I think you get the idea at: Text="{Binding ElementName=AllMatching, Path=SelectedItem.Name}".
Aditional Information
First:
You fire to early dude. Please first assign the value and then say its changed.
if (p_Name != value)
{
RaisePropertyChangedEvent("Name");
p_Name = value;
}
Second:
Use a ObservableCollection<DM_Data> to let your ListBox know about changes.
Third:
Use the posibility of Binding
Remove AllMatching.ItemsSource = ViewModel.AllData; and go like
<ListBox x:Name="AllMatching"
ItemsSource="{Binding Path=AllData}"
...
/>
And after all of this - please man check out some tutorials. And also refactor your code from VM_Data to DataViewModel thank you sir.

"PropertyChange" is not firing up when text is changed or inserted in Textbox present inside my view

I have two TextBoxes inside my View on which I am trying to implement a simple validation using MVVM design pattern.The issue is even when my ViewModel is implementing Inotification Changed interface and the property is bound tot the text property of the TextBox,on entering text propertyChange event never fires.I don't know where I have gone wrong.Please help.Its been bugging me for quite a while.
ViewModel :
class TextBoxValidationViewModel : ViewModelBase, IDataErrorInfo
{
private readonly TextBoxValidationModel _textbxValModel;
private Dictionary<string, bool> validProperties;
private bool allPropertiesValid = false;
private DelegateCommand exitCommand;
private DelegateCommand saveCommand;
public TextBoxValidationViewModel(TextBoxValidationModel newTextBoxValObj)
{
this._textbxValModel = newTextBoxValObj;
this.validProperties = new Dictionary<string, bool>();
this.validProperties.Add("BuyTh", false);
this.validProperties.Add("SellTh", false);
}
public string BuyTh
{
get { return _textbxValModel.BuyTh; }
set
{
if (_textbxValModel.BuyTh != value)
{
_textbxValModel.BuyTh = value;
base.OnPropertyChanged("BuyTh");
}
}
}
public string SellTh
{
get { return _textbxValModel.SellTh; }
set
{
if (_textbxValModel.SellTh != value)
{
_textbxValModel.SellTh = value;
base.OnPropertyChanged("SellTh");
}
}
}
public bool AllPropertiesValid
{
get { return allPropertiesValid; }
set
{
if (allPropertiesValid != value)
{
allPropertiesValid = value;
base.OnPropertyChanged("AllPropertiesValid");
}
}
}
public string this[string propertyName]
{
get
{
string error = (_textbxValModel as IDataErrorInfo)[propertyName];
validProperties[propertyName] = String.IsNullOrEmpty(error) ? true : false;
ValidateProperties();
CommandManager.InvalidateRequerySuggested();
return error;
}
}
public string Error
{
get
{
return (_textbxValModel as IDataErrorInfo).Error;
}
}
public ICommand ExitCommand
{
get
{
if (exitCommand == null)
{
exitCommand = new DelegateCommand(Exit);
}
return exitCommand;
}
}
public ICommand SaveCommand
{
get
{
if (saveCommand == null)
{
saveCommand = new DelegateCommand(Save);
}
return saveCommand;
}
}
#region private helpers
private void ValidateProperties()
{
foreach (bool isValid in validProperties.Values)
{
if (!isValid)
{
this.AllPropertiesValid = false;
return;
}
}
this.AllPropertiesValid = true;
}
private void Exit()
{
Application.Current.Shutdown();
}
private void Save()
{
_textbxValModel.Save();
}
}
}
#endregion
Model :
class TextBoxValidationModel : IDataErrorInfo
{
public string BuyTh { get; set; }
public string SellTh { get; set; }
public void Save()
{
//Insert code to save new Product to database etc
}
public string this[string propertyName]
{
get
{
string validationResult = null;
switch (propertyName)
{
case "BuyTh":
validationResult = ValidateName();
break;
case "SellTh":
validationResult = ValidateName();
break;
default:
throw new ApplicationException("Unknown Property being validated on Product.");
}
return validationResult;
}
}
public string Error
{
get
{
throw new NotImplementedException();
}
}
private string ValidateName()
{
return "Entered in validation Function";
}
}
}
ViewModelBase abstract Class :
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Application Start event code:
private void Application_Startup(object sender, StartupEventArgs e)
{
textboxvalwpf.Model.TextBoxValidationModel newTextBoxValObj = new Model.TextBoxValidationModel();
TextBoxValidation _txtBoxValView = new TextBoxValidation();
_txtBoxValView.DataContext = new textboxvalwpf.ViewModel.TextBoxValidationViewModel(newTextBoxValObj);
// _txtBoxValView.Show();
}
}
View Xaml code:
<Window x:Class="textboxvalwpf.TextBoxValidation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:c="clr-namespace:textboxvalwpf.Commands"
xmlns:local="clr-namespace:textboxvalwpf"
mc:Ignorable="d"
Title="TextBoxValidation" Height="300" Width="300">
<Grid>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="86,44,0,0" TextWrapping="Wrap" Text="{Binding Path=BuyTh, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
<TextBox x:Name="textBox1" HorizontalAlignment="Left" Height="23" Margin="88,121,0,0" TextWrapping="Wrap" Text="{Binding Path=SellTh,ValidatesOnDataErrors=True,UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
<Label x:Name="label_BuyTh" Content="Buy Th" HorizontalAlignment="Left" Margin="10,44,0,0" VerticalAlignment="Top" Width="71"/>
<Label x:Name="label_SellTh" Content="Sell Th" HorizontalAlignment="Left" Margin="10,117,0,0" VerticalAlignment="Top" Width="71"/>
</Grid>
</Window>
In App.xaml, you'll find a StartupUri attribute. By default, it looks like this:
<Application x:Class="WPFTest.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFTest"
StartupUri="MainWindow.xaml">
If you renamed MainWindow.xaml to TextBoxValidation.xaml, as I think you did, it'll look like this:
StartupUri="TextBoxValidation.xaml">
Your application will automatically create an instance of that StartupUri window and show it. That'll be the application's main window, so the application will shut down when it closes.
That instance won't have a viewmodel, because nothing in your code gives it one. You are creating your own instance of the window, giving it a viewmodel, and then doing nothing with it because another instance is being shown. I imagine you thought just creating the window was enough to show it, but that's not what's happening at all. Before you commented out the Show() call, you did have one window with a working viewmodel that did the validation and updated the viewmodel properties.
Let the App create the window the way it wants to.
A quick, simple fix is to delete that Startup event handler from App and move your viewmodel creation code into TextBoxValidation's constructor:
public TextBoxValidation()
{
InitializeComponent();
textboxvalwpf.Model.TextBoxValidationModel newTextBoxValObj = new Model.TextBoxValidationModel();
this.DataContext = new textboxvalwpf.ViewModel.TextBoxValidationViewModel(newTextBoxValObj);
}

WPF Data Binding and Templates

I am working on a project and I want to have a list box with a custom data template populate with user data. My question is, when I click on an item in the list box, how can I tell what item I selected? Basically, if I select "kevin", I want to display his data. If I select Dave, I want to display his data. I do not know how to get the data our after it is bound...
EDIT: I found a really great tutorial that covers this. A very hidden gem.
http://msdn.microsoft.com/en-us/library/aa480224.aspx
Bind SelectedItem of the ComboBox to any property.
<Window x:Class="ComboBoxSelectedItemBinding.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="500">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListBox x:Name="st"
ItemsSource="{Binding Path=Customers,Mode=TwoWay}" IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding Path=SelectedCustomer,Mode=TwoWay}"
Margin="0,38,0,80">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Text="{Binding Path=SelectedCustomer.Name}" Grid.Column="1" VerticalAlignment="Center" Margin="5"></TextBlock>
</Grid>
public partial class Window1 : Window, INotifyPropertyChanged
{
private ObservableCollection<Customer> customers;
public ObservableCollection<Customer> Customers
{
get
{
return customers;
}
set
{
customers = value;
NotifyPropertyChanged("Customers");
}
}
private Customer selectedCustomer;
public Customer SelectedCustomer
{
get
{
return selectedCustomer;
}
set
{
selectedCustomer = value;
NotifyPropertyChanged("SelectedCustomer");
}
}
public Window1()
{
Customers = new ObservableCollection<Customer>();
Customers.Add(new Customer() { ID = 1, Name = "Ravi", Salary = 1000 });
Customers.Add(new Customer() { ID = 99, Name = "Alex", Salary = 3000 });
Customers.Add(new Customer() { ID = 123, Name = "Steve", Salary = 100 });
Customers.Add(new Customer() { ID = 31, Name = "Alice", Salary = null });
InitializeComponent();
DataContext = this;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
public class Customer:INotifyPropertyChanged
{
private int id;
public int ID
{
get
{
return id;
}
set
{
id = value;
NotifyPropertyChanged("ID");
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
NotifyPropertyChanged("Name");
}
}
private decimal? salary;
public decimal? Salary
{
get
{
return salary;
}
set
{
salary = value;
NotifyPropertyChanged("Salary");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
The SelectedIndex property of the ListBox will correspond to the index of the selected item in the data source. So assuming you have bound to an IList you should be able to just use myDataSource[myListBox.SelectedIndex]. I'm assuming you aren't trying to support multiselect, in which case you can use the same concept, but the implementation is more complicated.
Depending if you let the user select one or many item in your ListBox you can just loop all Item and check if it selected. Here is a little example C# example (could be done in VB.NET):
for (int i = 0; i < MyListBox.Items.Count; i++)
{
if(MyListBox.Items[i].Selected)
//Do what you want with the item with : MyListBox.Items[i].Value
}
It sounds like you will only have one item selected at a time. If so, then I prefer to bind the ListBox.SelectedItem to a property on my ViewModel. If we assume that you bound the listbox to a collection of Person classes, then the property on the ViewModel would be of type Person. The listbox will set this property to the selected instance of Person.
Then, just bind the other portion of the UI that show's Kevin's data to the property on the ViewModel.
As long as you have INotifyPropertyChanged implemented on the properties, your UI should update automatically as you change the selected item in the listbox.

databinding combobox in Dataform Silverlight using MVVM in Update

I have Master/Detail – datagrid/dataform and after select item it shows in dataform for update, but I have a problem with databinding or populating combox with departments and set SelectedEmployee.departmentid as selectedvalue.
Here 2 questions now:
1. In EmployeeViewModel this code just doesn’t work and the question why?
private ObservableCollection<department> _departments;
public ObservableCollection<department> Departments
{
get { return _departments; }
set
{
_departments = value;
RaisePropertyChanged("Departments");
}
}
But this code works fine
private ObservableCollection<department> _departments;
public ObservableCollection<department> Departments
{
get {
if (_departments == null)
{
_departments = new ObservableCollection<department> {
new department()
{ id = 1, departmentname = "Technical " + 1, },
new department()
{ id = 2, departmentname = "Technical " + 2, },
new department()
{ id = 3, departmentname = "Technical " + 3, }
};
}
return _departments;
}
set
{
_departments = value;
RaisePropertyChanged("Departments");
}
}
2. Behavior of Combobox inside and outside DataForm is different. Outside it works, inside it doesn’t. I think here need to use Source in ItemsSource, but I don’t know how. So there is another question is how to fix it?
employeeView.xaml
<navigation:Page xmlns:local="clr-namespace:departmentTechManager"
x:Class="departmentTechManager.Views.employeeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="820" d:DesignHeight="780"
Title="employees"
Style="{StaticResource PageStyle}"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:mvvmlightcmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL4"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:dataformtoolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
DataContext="{Binding employeeStatic, Source={StaticResource Locator}}">
<data:DataGrid Grid.Row="0" x:Name="dgEmployees" CanUserSortColumns="true"
IsReadOnly="true" AutoGenerateColumns="true"
ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee, Mode=TwoWay}" Margin="0,36,-123,0"></data:DataGrid>
<dataformtoolkit:DataForm x:Name="dfDetails"
CurrentItem="{Binding SelectedEmployee}" AutoGenerateFields="False"
CommitButtonContent="Save" CommandButtonsVisibility="Edit, Commit, Cancel">
<dataformtoolkit:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataformtoolkit:DataField Label="name">
<TextBox Text="{Binding name, Mode=TwoWay}" /></dataformtoolkit:DataField>
<dataformtoolkit:DataField Label="departments">
<ComboBox ItemsSource="{Binding Departments}"
DisplayMemberPath="departmentname"
SelectedValuePath="id"
SelectedValue="{Binding Path=SelectedEmployee.departmentid, Mode=TwoWay}" />
</dataformtoolkit:DataField>
</StackPanel>
</DataTemplate>
</dataformtoolkit:DataForm.EditTemplate>
<i:Interaction.Triggers><i:EventTrigger EventName="EditEnded">
<mvvmlightcmd:EventToCommand Command="{Binding SaveEmployeesCommand}"/>
</i:EventTrigger></i:Interaction.Triggers>
</dataformtoolkit:DataForm>
In ViewModelLocator.cs:
public ViewModelLocator()
{
_sp = ServiceProviderBase.Instance;
Createdepartment();
Createemployee();
}
#region EmployeeViewModel
private static EmployeeViewModel _employee;
public static EmployeeViewModel employeeStatic
{
get
{
if (_employee == null)
{
Createemployee();
}
return _employee;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public EmployeeViewModel employee
{
get
{
return employeeStatic;
}
}
public static void Clearemployee()
{
//do it later
//_employee.Cleanup();
_employee = null;
}
public static void Createemployee()
{
if (_employee == null)
{
_employee = new EmployeeViewModel(_sp.PageConductor, _sp.EmployeeDataService);
}
}
#endregion
#region DepartmentViewModel
private static DepartmentViewModel _department;
public static DepartmentViewModel departmentStatic
{
get
{
if (_department == null)
{
Createdepartment();
}
return _department;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public DepartmentViewModel department
{
get
{
return departmentStatic;
}
}
public static void Cleardepartment()
{
//do it later
//_department.Cleanup();
_department = null;
}
public static void Createdepartment()
{
if (_department == null)
{
_department = new DepartmentViewModel(_sp.PageConductor, _sp.DepartmentDataService);
}
}
#endregion
Can somebody help me?
combox is just empty. but now i can populate it with Departments like so:
departmentTechManagerDomainService.metadata.cs
[MetadataTypeAttribute(typeof(employee.employeeMetadata))]
public partial class employee
{
[Include]
public department department { get; set; }
public Nullable<int> departmentid { get; set; }
public string name { get; set; }
}
departmentTechManagerDomainService.cs
public IQueryable<employee> GetEmployees()
{return this.ObjectContext.employees.Include("department").OrderBy(e=>e.name);}
here is ViewModel code:
private ObservableCollection<department> _departments;
public ObservableCollection<department> Departments
{
get { return _departments; }
set
{
_departments = value;
RaisePropertyChanged("Departments");
}
}
private department _selectedDepartment;
public department SelectedDepartment
{
get { return _selectedDepartment; }
set
{
_selectedDepartment = value;
RaisePropertyChanged("SelectedDepartment");
}
}
private void InitializeModels()
{
Employees = new ObservableCollection<employee>();
SelectedEmployee = new employee();
NewEmployee = new employee();
//new
Departments = new ObservableCollection<department>();
SelectedDepartment = new department();
}
private void GetEmployeesCallback(IEnumerable<employee> employees)
{
if (employees != null)
{
foreach (var employee in employees)
{
Employees.Add(employee);
//new
if (!Departments.Contains(employee.department))
Departments.Add(employee.department);
}
if (Employees.Count > 0)
{
SelectedEmployee = Employees[0];
}
}
}
I make Departments distinct, but here is only departments those already have been selected, but here aren't those that haven't been selected yet, and still combobox is not populated with departments in DataForm. ?!
2nd question - it looks like combobox inside and outside of dataform receives different DataContext and hence trying to locate Departments properties on different sources. It is not clear how to fix it since you haven't shown most of your ViewModels.
Pay attention to the VS output window, it usually provides very detailed info regarding binding errors and I'm assuming there are binding errors in your case.
Try modifying your department related bindings in following way:
<ComboBox ItemsSource="{Binding DataContext.Departments, RelativeSoruce={RelativeSource AncestorType={x:Type localViews:employeeView}}}" />
Where localViews should be an xml-namespace for departmentTechManager.Views. Try the same trick for SelectedItem binding.
I've got the solution for this question. here it is.
In Edit Template, Source must need to be mention with ViewModel Name
<ComboBox Grid.Row="2" Grid.Column="1"
ItemsSource="{Binding Path=Accounts, Source={StaticResource MyAccountViewModel}, Mode=TwoWay}" />

Mutually Exclusive comboboxes that binds to same data source - MVVM implementation

I'm not sure my Title is right but this is the problem I am facing now.. I have the below XAML code..
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=AvailableFields}"
SelectedItem="{Binding Path=SelectedField}"
></ComboBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
What this basically does is, If my data source contains ten items, this is going to generate 10 row of comboboxes and all comboboxes are bounded to the same itemsource.
Now my requirement is Once an item is selected in the first combo box, that item should not be available in the subsequent combo boxes. How to satisfy this requirement in MVVM and WPF?
This turned out to be harder than I thought when I started coding it. Below sample does what you want. The comboboxes will contain all letters that are still available and not selected in another combobox.
XAML:
<Window x:Class="TestApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=SelectedLetters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path=AvailableLetters}"
SelectedItem="{Binding Path=Letter}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
namespace TestApp
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new VM();
}
}
public class VM : INotifyPropertyChanged
{
public VM()
{
SelectedLetters = new List<LetterItem>();
for (int i = 0; i < 10; i++)
{
LetterItem letterItem = new LetterItem();
letterItem.PropertyChanged += OnLetterItemPropertyChanged;
SelectedLetters.Add(letterItem);
}
}
public List<LetterItem> SelectedLetters { get; private set; }
private void OnLetterItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != "Letter")
{
return;
}
foreach (LetterItem letterItem in SelectedLetters)
{
letterItem.RefreshAvailableLetters(SelectedLetters);
}
}
public event PropertyChangedEventHandler PropertyChanged;
public class LetterItem : INotifyPropertyChanged
{
static LetterItem()
{
_allLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Select(c => c.ToString());
}
public LetterItem()
{
AvailableLetters = _allLetters;
}
public void RefreshAvailableLetters(IEnumerable<LetterItem> letterItems)
{
AvailableLetters = _allLetters.Where(c => !letterItems.Any(li => li.Letter == c) || c == Letter);
}
private IEnumerable<string> _availableLetters;
public IEnumerable<string> AvailableLetters
{
get { return _availableLetters; }
private set
{
_availableLetters = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("AvailableLetters"));
}
}
}
private string _letter;
public string Letter
{
get { return _letter; }
set
{
if (_letter == value)
{
return;
}
_letter = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Letter"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private static readonly IEnumerable<string> _allLetters;
}
}
}
This functionality is not provided by WPF, but it can be implemented using some custom coding.
I've created 3 ViewModel classes:
PreferencesVM - This will be our DataContext. It contains the master list of options which can appear in the ComboBoxes, and also contains a SelectedOptions property, which keeps track of which items are selected in the various ComboBoxes. It also has a Preferences property, which we will bind our ItemsControl.ItemsSource to.
PreferenceVM - This represents one ComboBox. It has a SelectedOption property, which ComboBox.SelectedItem is bound to. It also has a reference to PreferencesVM, and a property named Options (ComboBox.ItemsSource is bound to this), which returns the Options on PreferencesVM via a filter which checks if the item may be displayed in the ComboBox.
OptionVM - Represents a row in the ComboBox.
The following points form the key to the solution:
When PreferenceVM.SelectedOption is set (ie a ComboBoxItem is selected), the item is added to the PreferencesVM.AllOptions collection.
PreferenceVM handles Preferences.SelectedItems.CollectionChanged, and triggers a refresh by raising PropertyChanged for the Options property.
PreferenceVM.Options uses a filter to decide which items to return - which only allows items which are not in PreferencesVM.SelectedOptions, unless they are the SelectedOption.
What I've described above might be enough to get you going, but to save you the headache I'll post my code below.
PreferencesVM.cs:
public class PreferencesVM
{
public PreferencesVM()
{
PreferenceVM pref1 = new PreferenceVM(this);
PreferenceVM pref2 = new PreferenceVM(this);
PreferenceVM pref3 = new PreferenceVM(this);
this._preferences.Add(pref1);
this._preferences.Add(pref2);
this._preferences.Add(pref3);
//Only three ComboBoxes, but you can add more here.
OptionVM optRed = new OptionVM("Red");
OptionVM optGreen = new OptionVM("Green");
OptionVM optBlue = new OptionVM("Blue");
_allOptions.Add(optRed);
_allOptions.Add(optGreen);
_allOptions.Add(optBlue);
}
private ObservableCollection<OptionVM> _selectedOptions =new ObservableCollection<OptionVM>();
public ObservableCollection<OptionVM> SelectedOptions
{
get { return _selectedOptions; }
}
private ObservableCollection<OptionVM> _allOptions = new ObservableCollection<OptionVM>();
public ObservableCollection<OptionVM> AllOptions
{
get { return _allOptions; }
}
private ObservableCollection<PreferenceVM> _preferences = new ObservableCollection<PreferenceVM>();
public ObservableCollection<PreferenceVM> Preferences
{
get { return _preferences; }
}
}
PreferenceVM.cs:
public class PreferenceVM:INotifyPropertyChanged
{
private PreferencesVM _preferencesVM;
public PreferenceVM(PreferencesVM preferencesVM)
{
_preferencesVM = preferencesVM;
_preferencesVM.SelectedOptions.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedOptions_CollectionChanged);
}
void SelectedOptions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this,new PropertyChangedEventArgs("Options"));
}
private OptionVM _selectedOption;
public OptionVM SelectedOption
{
get { return _selectedOption; }
set
{
if (value == _selectedOption)
return;
if (_selectedOption != null)
_preferencesVM.SelectedOptions.Remove(_selectedOption);
_selectedOption = value;
if (_selectedOption != null)
_preferencesVM.SelectedOptions.Add(_selectedOption);
}
}
private ObservableCollection<OptionVM> _options = new ObservableCollection<OptionVM>();
public IEnumerable<OptionVM> Options
{
get { return _preferencesVM.AllOptions.Where(x=>Filter(x)); }
}
private bool Filter(OptionVM optVM)
{
if(optVM==_selectedOption)
return true;
if(_preferencesVM.SelectedOptions.Contains(optVM))
return false;
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
}
OptionVM.cs:
public class OptionVM
{
private string _name;
public string Name
{
get { return _name; }
}
public OptionVM(string name)
{
_name = name;
}
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new PreferencesVM();
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication64.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl ItemsSource="{Binding Path=Preferences}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=Options}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedOption}"></ComboBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
**Note that to reduce lines of code, my provided solution only generates 3 ComboBoxes (not 10).

Resources