I cannot understand why my third nested DataBinding in WPF does not work. I use Entity Framework and Sql Server 2012, and the following are my entities. An application can have multiple accounts. There is an account table and an application table.
ENTITIES
1. Applications
2. Accounts
ViewModels
1. ApplicationListViewModel
2. ApplicationViewModel
3. AccountListViewModel
4. AccountViewModel
In my usercontrol, I am trying to do the following:
1. Using combobox, select an application using ApplicationListViewModel ( Work )
2. In the selected application, display all accounts in the datagrid ( Work )
3. With the selected account, information about the specific account is displayed. ( Does not display information about the selected account )
<UserControl.Resources> <vm:ApplicationListViewModel x:Key="AppList" /> </UserControl.Resources> <StackPanel DataContext="{Binding Source={StaticResource AppList}}"> <Grid> <Grid.RowDefinitions> ... </Grid.ColumnDefinitions> <StackPanel Grid.Row="0" Grid.Column="0"> <GroupBox Header="View all"> <StackPanel> <ComboBox x:Name="cbxApplicationList" ItemsSource="{Binding Path=ApplicationList}" DisplayMemberPath="Title" SelectedValuePath="Id" SelectedItem="{Binding Path=SelectedApplication, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True" /> <DataGrid x:Name="dtgAccounts" Height="Auto" Width="auto" AutoGenerateColumns="False" DataContext="{Binding SelectedApplication.AccountLVM}" ItemsSource="{Binding Path=AccountList}" SelectedItem="{Binding SelectedAccount, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True"> <DataGrid.Columns> <DataGridTextColumn Header="Title" Binding="{Binding Path=Title}"></DataGridTextColumn> </DataGrid.Columns> </DataGrid> </StackPanel> </GroupBox> </StackPanel> <StackPanel Grid.Row="0" Grid.Column="1" > <GroupBox x:Name="grpBoxAccountDetails" Header="New Account" > <StackPanel DataContext="{Binding SelectedApplication.AccountLVM.SelectedAccount}" > <Grid> <Grid.RowDefinitions> ... </Grid.ColumnDefinitions> <TextBlock x:Name="lblApplication" Grid.Row="0" Grid.Column="0" >Application</TextBlock> <ComboBox x:Name="cbxApplication" Grid.Row="0" Grid.Column="1" DataContext="{Binding Source={StaticResource AppList}}" ItemsSource="{Binding ApplicationList}" DisplayMemberPath="Title" SelectedValuePath="Id" SelectedValue="{Binding SelectedApplication.AccountLVM.SelectedAccount.ApplicationId}"> </ComboBox> <TextBlock x:Name="lblTitle" Grid.Row="0" Grid.Column="0" >Title</TextBlock> <TextBox x:Name="txtTitle" Grid.Row="0" Grid.Column="1" Height="30" Width="200" Text="{Binding Title}" DataContext="{Binding Mode=OneWay}"></TextBox> <Button Grid.Row="1" Grid.Column="0" Command="{Binding AddAccount}">Add</Button> </Grid> </StackPanel> </GroupBox> </StackPanel> </Grid> </StackPanel>
ApplicationListViewModel
class ApplicationListViewModel : ViewModelBase { myEntities context = new myEntities(); private static ApplicationListViewModel instance = null; private ObservableCollection<ApplicationViewModel> _ApplicationList = null; public ObservableCollection<ApplicationViewModel> ApplicationList { get { return GetApplications(); } set { _ApplicationList = value; OnPropertyChanged("ApplicationList"); } }
ApplicationViewModel
class ApplicationViewModel : ViewModelBase { private myEntities context = new myEntities(); private ApplicationViewModel originalValue; public ApplicationViewModel() { } public ApplicationViewModel(Application acc) {
AccountListViewModel
class AccountListViewModel : ViewModelBase { myEntities context = new myEntities(); private static AccountListViewModel instance = null; private ObservableCollection<AccountViewModel> _accountList = null; public ObservableCollection<AccountViewModel> AccountList { get { if (_accountList != null) return _accountList; else return GetAccounts(); } set { _accountList = value; OnPropertyChanged("AccountList"); } } private AccountViewModel selectedAccount = null; public AccountViewModel SelectedAccount { get { return selectedAccount; } set { selectedAccount = value; OnPropertyChanged("SelectedAccount"); } } public AccountListViewModel() { this._accountList = GetAccounts(); } internal ObservableCollection<AccountViewModel> GetAccounts() { if (_accountList == null) _accountList = new ObservableCollection<AccountViewModel>(); _accountList.Clear(); foreach (Account item in context.Accounts) { AccountViewModel a = new AccountViewModel(item); _accountList.Add(a); } return _accountList; } public static AccountListViewModel Instance() { if (instance == null) instance = new AccountListViewModel(); return instance; } }
AccountViewModel. I simply exclude all initialization logic in the viewmodel for simplicity.
class AccountViewModel : ViewModelBase { private myEntites context = new myEntities(); private AccountViewModel originalValue; public AccountViewModel() { } public AccountViewModel(Account acc) {
Edit1:
When I bind data to view SelectedAccount details with a text box, it does not display text.
1. Ability to bind data to ApplicationListViewModel for Combobox.
2. Successfully Bind to view AccountList based on SelectedApplication
3. Failed to bind to the selected account in AccountListViewModel.
I think that in the next line it does not show any information about the selected account. I checked all the data binding syntaxes. In properties, I can view the corresponding DataContext and bind it to the properties. But the text is not displayed. When I select each individual record in the DataGrid, I can debug the call and select the object, but somehow this object does not appear in the text box at the very end.
DataContext="{Binding SelectedApplication.AccountLVM.SelectedAccount}"
Edit2:
Based on the suggestion in the comment below, I tried snoop and was able to see the header text box row highlighted in red. I am trying to change the binding property of Path and datacontext, but still not working. When I tried to click "Delve Binding Expression", it gave me an unhandled exception. I do not know what this means if it came from Snoop.
Edit3:
I took screenshots of the DataContext property for the StackPanel for the Account Information section and the text property of the text field.

Decision:
Based on the suggestions below, I made the following changes to my decision and made it simpler. I made it unnecessarily complicated.
1. AccountsViewModel
2. AccountViewModel
3. ApplicationViewModel
Now I created properties as SelectedApplication , SelectedAccount with just one AccountsViewModel . The entire complex DataContext syntax has been removed, and now there is only one DataContext on the xaml page.
Simplified code.
class AccountsViewModel: ViewModelBase { myEntities context = new myEntities(); private ObservableCollection<ApplicationViewModel> _ApplicationList = null; public ObservableCollection<ApplicationViewModel> ApplicationList { get { if (_ApplicationList == null) { GetApplications(); } return _ApplicationList; } set { _ApplicationList = value; OnPropertyChanged("ApplicationList"); } } internal ObservableCollection<ApplicationViewModel> GetApplications() { if (_ApplicationList == null) _ApplicationList = new ObservableCollection<ApplicationViewModel>(); else _ApplicationList.Clear(); foreach (Application item in context.Applications) { ApplicationViewModel a = new ApplicationViewModel(item); _ApplicationList.Add(a); } return _ApplicationList; }
XAML side
<UserControl.Resources> <vm:AccountsViewModel x:Key="ALVModel" /> </UserControl.Resources> <StackPanel DataContext="{Binding Source={StaticResource ALVModel}}" Margin="0,0,-390,-29"> <StackPanel> <ComboBox x:Name="cbxApplicationList" ItemsSource="{Binding Path=ApplicationList}" DisplayMemberPath="Title" SelectedValuePath="Id" SelectedItem="{Binding Path=SelectedApplication, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True"></ComboBox> <DataGrid x:Name="dtgAccounts" Height="Auto" Width="auto" AutoGenerateColumns="False" ItemsSource="{Binding Path=AccountList}" SelectedItem="{Binding SelectedAccount, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True" > <DataGrid.Columns> <DataGridTextColumn Header="Title" Binding="{Binding Path=Title}"></DataGridTextColumn> <DataGridTextColumn Header="CreatedDate" Binding="{Binding Path=CreatedDate}"></DataGridTextColumn> <DataGridTextColumn Header="LastModified" Binding="{Binding Path=LastModifiedDate}"></DataGridTextColumn> </DataGrid.Columns> </DataGrid> </StackPanel> <StackPanel Height="Auto" Width="300" HorizontalAlignment="Left" DataContext="{Binding Path=SelectedAccount}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="30"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"></ColumnDefinition> <ColumnDefinition Width="200"></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock x:Name="lblTitle" Grid.Row="0" Grid.Column="0" >Title</TextBlock> <TextBox x:Name="txtTitle" Grid.Row="0" Grid.Column="1" Height="30" Width="200" Text="{Binding Title}"></TextBox> </Grid> </StackPanel> </StackPanel>
I did not understand the concept of MVVM properly. I tried to build everything modular, and in the end I messed it up.