[英]Nested Data Binding using MVVM in WPF not working
我無法弄清楚為什么WPF中的第三個嵌套數據綁定無法正常工作。 我正在使用Entity Framework和Sql Server 2012,以下是我的實體。 應用程序可以有多個帳戶。 有一個帳戶表和一個應用程序表。
實體
1.申請
2.賬目
的ViewModels
1. ApplicationListViewModel
2. ApplicationViewModel
3. AccountListViewModel
4. AccountViewModel
在我的usercontrol中,我嘗試執行以下操作:
1.使用組合框使用ApplicationListViewModel選擇應用程序( 工作 )
2.在選定的應用程序中顯示數據網格中的所有帳戶( 工作 )
3.選擇帳戶后顯示有關特定帳戶的詳細信息。( 不顯示所選帳戶的詳細信息 )
<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>
<!-- All Applications List -->
<ComboBox x:Name="cbxApplicationList"
ItemsSource="{Binding Path=ApplicationList}"
DisplayMemberPath="Title" SelectedValuePath="Id"
SelectedItem="{Binding Path=SelectedApplication, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="True" />
<!-- Selected Application Accounts -->
<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" >
<!-- Selected Account Details -->
<!-- DataContext binding does not appear to work -->
<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");
}
}
//public ObservableCollection<ApplicationViewModel> Cu
private ApplicationViewModel selectedApplication = null;
public ApplicationViewModel SelectedApplication
{
get
{
return selectedApplication;
}
set
{
selectedApplication = value;
OnPropertyChanged("SelectedApplication");
}
}
//private ICommand showAddCommand;
public ApplicationListViewModel()
{
this._ApplicationList = GetApplications();
}
internal ObservableCollection<ApplicationViewModel> GetApplications()
{
if (_ApplicationList == null)
_ApplicationList = new ObservableCollection<ApplicationViewModel>();
_ApplicationList.Clear();
foreach (Application item in context.Applications)
{
ApplicationViewModel a = new ApplicationViewModel(item);
_ApplicationList.Add(a);
}
return _ApplicationList;
}
public static ApplicationListViewModel Instance()
{
if (instance == null)
instance = new ApplicationListViewModel();
return instance;
}
}
ApplicationViewModel
class ApplicationViewModel : ViewModelBase
{
private myEntities context = new myEntities();
private ApplicationViewModel originalValue;
public ApplicationViewModel()
{
}
public ApplicationViewModel(Application acc)
{
//Initialize property values
this.originalValue = (ApplicationViewModel)this.MemberwiseClone();
}
public ApplicationListViewModel Container
{
get { return ApplicationListViewModel.Instance(); }
}
private AccountListViewModel _AccountLVM = null;
public AccountListViewModel AccountLVM
{
get
{
return GetAccounts();
}
set
{
_AccountLVM = value;
OnPropertyChanged("AccountLVM");
}
}
internal AccountListViewModel GetAccounts()
{
_AccountLVM = new AccountListViewModel();
_AccountLVM.AccountList.Clear();
foreach (Account i in context.Accounts.Where(x=> x.ApplicationId == this.Id))
{
AccountViewModel account = new AccountViewModel(i);
account.Application = this;
_AccountLVM.AccountList.Add(account);
}
return _AccountLVM;
}
}
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。 為簡單起見,我在viewmodel中省略了所有其他初始化邏輯。
class AccountViewModel : ViewModelBase
{
private myEntites context = new myEntities();
private AccountViewModel originalValue;
public AccountViewModel()
{
}
public AccountViewModel(Account acc)
{
//Assign property values.
this.originalValue = (AccountViewModel)this.MemberwiseClone();
}
public AccountListViewModel Container
{
get { return AccountListViewModel.Instance(); }
}
public ApplicationViewModel Application
{
get;
set;
}
}
EDIT1:
當我使用數據綁定來查看帶有文本框的SelectedAccount的詳細信息時,它不會顯示任何文本。
1.能夠將ApplicationListViewModel數據綁定到Combobox。
2.成功綁定以基於SelectedApplication查看AccountList
3.無法綁定到AccountListViewModel中的SelectedAcount。
我認為在以下行中它沒有顯示有關所選帳戶的任何詳細信息。 我檢查了所有數據綁定語法。 在屬性中,我能夠查看適當的DataContext並綁定到屬性。 但它沒有顯示任何文字。 當我在DataGrid中選擇每個單獨的記錄時,我能夠調試該調用並選擇該對象,但不知何故該對象未在最后的文本框中顯示。
DataContext="{Binding SelectedApplication.AccountLVM.SelectedAccount}"
EDIT2:
根據下面評論中的建議,我嘗試了窺探,並能夠看到標題文本框行以紅色突出顯示。 我試圖更改綁定路徑屬性和datacontext但仍然無法正常工作。 當我試圖點擊“Delve Binding Expression”時,它給了我未處理的異常。 如果它來自Snoop,我不知道這意味着什么。
EDIT3:
我已經為“帳戶詳細信息”部分的StackPanel和文本框的文本屬性截取了DataContext屬性的屏幕截圖。
解:
根據以下建議,我對我的解決方案進行了以下更改,並使其變得更加簡單。 我做得太不必要了。
1. AccountsViewModel
2. AccountViewModel
3. ApplicationViewModel
現在我只在一個AccountsViewModel
創建了SelectedApplication
, SelectedAccount
屬性。 刪除了所有復雜的DataContext語法,現在xaml頁面中只有一個DataContext。
簡化代碼。
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;
}
//Selected Application Property
private ApplicationViewModel selectedApplication = null;
public ApplicationViewModel SelectedApplication
{
get
{
return selectedApplication;
}
set
{
selectedApplication = value;
this.GetAccounts();
OnPropertyChanged("SelectedApplication");
}
}
private ObservableCollection<AccountViewModel> _accountList = null;
public ObservableCollection<AccountViewModel> AccountList
{
get
{
if (_accountList == null)
GetAccounts();
return _accountList;
}
set
{
_accountList = value;
OnPropertyChanged("AccountList");
}
}
//public ObservableCollection<AccountViewModel> Cu
private AccountViewModel selectedAccount = null;
public AccountViewModel SelectedAccount
{
get
{
return selectedAccount;
}
set
{
selectedAccount = value;
OnPropertyChanged("SelectedAccount");
}
}
internal ObservableCollection<AccountViewModel> GetAccounts()
{
if (_accountList == null)
_accountList = new ObservableCollection<AccountViewModel>();
else
_accountList.Clear();
foreach (Account item in context.Accounts.Where(x => x.ApplicationId == this.SelectedApplication.Id))
{
AccountViewModel a = new AccountViewModel(item);
_accountList.Add(a);
}
return _accountList;
}
}
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>
我沒有正確理解MVVM概念。 我試圖建立模塊化的所有東西,最后我搞砸了。
我懷疑你的問題與每次調用AccountLVM
的setter時返回一個新的 ObservableCollection
的事實有關,並且你沒有提出你的PropertyChange
通知,因此任何現有的綁定都不會更新
public AccountListViewModel AccountLVM
{
get
{
return GetAccounts();
}
set
{
_AccountLVM = value;
OnPropertyChanged("AccountLVM");
}
}
internal AccountListViewModel GetAccounts()
{
_AccountLVM = new AccountListViewModel();
_AccountLVM.AccountList.Clear();
foreach (Account i in context.Accounts.Where(x=> x.ApplicationId == this.Id))
{
AccountViewModel account = new AccountViewModel(i);
account.Application = this;
_AccountLVM.AccountList.Add(account);
}
return _AccountLVM;
}
我發現你的綁定非常令人困惑並且很難遵循,但是我認為無論何時進行評估
DataContext="{Binding SelectedApplication.AccountLVM.SelectedAccount}"
它正在創建一個新的 AccountLVM
,它沒有設置SelectedAccount
屬性。
您根本看不到現有的DataGrid.SelectedItem
更改,因為它仍然綁定到舊的 AccountLVM
因為_accountLVM
更改時沒有引發PropertyChange
通知,因此綁定不知道更新。
但是其他一些與您的代碼相關的雜項:
除非您還為屬性的公共版本引發PropertyChange通知,否則請勿更改屬性的私有版本。 這適用於構造函數和GetXxxxx()
等GetAccounts()
方法。
不要從getter返回方法調用。 而是使用方法調用設置值,如果它為null,則返回私有屬性。
public AccountListViewModel AccountLVM { get { if (_accountLVM == null) GetAccounts(); // or _accountLVM = GetAccountLVM(); return _accountLVM; } set { ... } }
將DataContext
設置在如此多的控件中真的很令人困惑。 DataContext
是UI背后的數據層,如果您的UI只是簡單地反映了數據層,並且必須遍布整個地方來獲取數據,那么數據層就很難實現。
如果必須對當前數據上下文以外的其他內容進行綁定,請在立即更改DataContext
之前嘗試使用其他綁定屬性指定其他綁定Source
。 這是使用ElementName
屬性設置綁定源的示例:
<TextBox x:Name="txtTitle" ... Text="{Binding ElementName=dtgAccounts, Path=SelectedItem.Title}" />
繼承了DataContext
,因此您不需要編寫DataContext="{Binding }"
您可能需要考慮重寫您的父ViewModel,以便您可以像這樣設置XAML,而無需所有額外的DataContext
綁定或3部分嵌套屬性。
<ComboBox ItemsSource="{Binding ApplicationList}" SelectedItem="{Binding SelectedApplication}" /> <DataGrid ItemsSource="{Binding SelectedApplication.Accounts}" SelectedItem="{Binding SelectedAccount}" /> <StackPanel DataContext="{Binding SelectedAccount}"> ... </StackPanel>
如果您是DataContext
新手或者正在努力理解它,我建議您在我的博客上閱讀這篇文章 ,以便更好地了解它是什么以及它是如何工作的。
這個Binding
方法的一個主要問題是,只有在您的案例SelectedAccount
的最后一個屬性值發生更改時,才會更新該值。 BindingExpression
不會監視其他級別,因此如果更改了SelectedApplication.AccountLVM
則DataContext
將不會注意到SelectedAccount
的差異,因為綁定仍在“正在觀察”舊引用,並且您正在修改VM中的另一個引用。
所以我認為在應用程序的開始時, SelectedApplication
為null並且ComboBox
的Binding
沒有注意到它的變化。 嗯,我想到了另一個綁定解決方案,但我找不到一個。 所以我建議你創建一個額外的屬性來反映ApplicationListViewModel
類中的SelectedAccount
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.