WPF中使用MVVM进行嵌套数据绑定不起作用

4
我无法确定为什么我的WPF中的第三个嵌套数据绑定无法工作。我正在使用Entity Framework和Sql Server 2012,并且以下是我的实体。一个应用程序可以有多个帐户。有一个帐户表格和一个应用程序表格。 实体: 1. 应用程序 2. 帐户 视图模型: 1. 应用程序列表视图模型 2. 应用程序视图模型 3. 帐户列表视图模型 4. 帐户视图模型
在我的用户控件中,我试图做到以下几点: 1. 使用ApplicationListViewModel来选择应用程序(有效) 2. 选择应用程序后,在datagird中显示所有帐户(有效) 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>

应用程序列表视图模型

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

应用程序视图模型

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


    }

账户列表视图模型

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

编辑1:
当我将数据绑定到视图以使用文本框显示所选账户的详细信息时,它没有显示任何文本。
1. 可以将ApplicationListViewModel数据绑定到Combobox中。
2. 成功基于SelectedApplication将AccountList绑定到视图中。
3. 无法将AccountListViewModel中的SelectedAcount绑定。 我认为在以下一行代码中,它没有显示所选账户的任何详细信息。我已检查了所有的数据绑定语法。在属性中,我能够查看合适的DataContext并绑定到属性中。但它没有显示任何文本。当我在DataGrid中选择每个单独的记录时,我能够调试该调用并选择对象,但是该对象不会显示在最后的文本框中。
DataContext="{Binding SelectedApplication.AccountLVM.SelectedAccount}"

编辑2:
根据下面评论中的建议,我尝试了Snoop,并成功看到了用红色突出显示的标题文本框行。我正在尝试更改绑定路径属性和数据上下文,但仍然不起作用。当我试图点击“深入绑定表达式”时,它给了我未处理的异常。我不知道这是什么意思,因为它来自于Snoop。

编辑3:
我已经对账户详细信息部分的StackPanel的DataContext属性和文本框的文本属性进行了截屏。

enter image description here

解决方案:
基于以下建议,我对我的解决方案进行了以下更改,使其变得更加简单。我将它变得不必要地复杂化了。
1. AccountsViewModel
2. AccountViewModel
3. ApplicationViewModel

现在我已经创建了属性SelectedApplicationSelectedAccount,全部都在一个AccountsViewModel中。删除了所有复杂的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的概念。我试图构建所有模块化,最终却搞砸了。


3
那么,到底是什么出了问题? - Will Custode
抱歉之前匆忙发布,没有来得及审查。我道歉。 - Mitul
2
在运行时使用Snoop检查您的DataContext/Binding。 - blindmeis
为了提供更多信息,AccountLVM实际上是我在ApplicationViewModel中创建的AccountListViewModel,它过滤帐户并在AccountListViewModel中重新创建帐户列表。它会在Combobox选择时重新创建,但不会在datagrid选择更改时重新创建。 - Mitul
先生,我故意省略了所有属性以使问题长度更小。对此我感到抱歉。但是它在我的ViewModel中。 - Mitul
显示剩余4条评论
2个回答

4

我怀疑你的问题与每次调用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属性。

由于没有触发PropertyChange通知,因此您不会看到现有的DataGrid.SelectedItem发生任何变化,因为它仍然绑定到旧的AccountLVM上,因此绑定不知道要更新。

但是,还有一些与您的代码相关的其他杂项:

  • Don't change the private version of the property unless you also raise the PropertyChange notification for the public version of the property. This applies to both your constructors and your GetXxxxx() methods like GetAccounts().

  • Don't return a method call from your getter. Instead set the value using your method call if it's null, and return the private property afterwards.

    public AccountListViewModel AccountLVM
    {
        get
        {
            if (_accountLVM == null)
                GetAccounts(); // or _accountLVM = GetAccountLVM();
    
            return _accountLVM;
        }
        set { ... }
    }
    
  • It's really confusing to have the DataContext set in so many controls. The DataContext is the data layer behind your UI, and it's easiest if your UI simply reflects the data layer, and having to go all over the place to get your data makes the data layer really hard to follow.

  • If you must make a binding to something other than the current data context, try to use other binding properties to specify a different binding Source before immediately going to change the DataContext. Here's an example using the ElementName property to set the binding source:

    <TextBox x:Name="txtTitle" ...
             Text="{Binding ElementName=dtgAccounts, Path=SelectedItem.Title}" />
    
  • The DataContext in inherited, so you don't ever need to write DataContext="{Binding }"

  • You may want to consider re-writing your parent ViewModel so you can setup XAML like this, without all the extra DataContext bindings or 3-part nested properties.

    <ComboBox ItemsSource="{Binding ApplicationList}"
              SelectedItem="{Binding SelectedApplication}" />
    
    <DataGrid ItemsSource="{Binding SelectedApplication.Accounts}"
              SelectedItem="{Binding SelectedAccount}" />
    
    <StackPanel DataContext="{Binding SelectedAccount}">
       ...
    </StackPanel>
    

如果你对DataContext还不熟悉或者有些困惑,我推荐阅读我博客上的这篇文章,以便更好地理解它是什么以及如何使用。


我将重构我的代码并发布我的发现。非常感谢Rachel的建议和您的时间。 - Mitul

2
这种绑定方法的一个主要问题是,只有在最后一个属性值(在您的情况下是SelectedAccount)更改时,该值才会被更新。其他级别不受BindingExpression监视,因此如果例如SelectedApplication.AccountLVM发生更改,则DataContext将不会注意到SelectedAccount中的差异,因为绑定仍然“观察”旧引用并且您正在修改VM中的另一个引用。
因此,我认为在应用程序的开始处,SelectedApplication为空,并且ComboBox绑定没有注意到它的更改。嗯,我考虑过另一种绑定解决方案,但我找不到合适的。因此,我建议您在ApplicationListViewModel类中创建一个额外的属性来反映SelectedAccount

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接