在WPF中使用MVVM的嵌套数据绑定无法正常工作

时间:2013-03-18 21:56:32

标签: c# wpf entity-framework mvvm

我无法弄清楚为什么WPF中的第三个嵌套数据绑定无法正常工作。我正在使用Entity Framework和Sql Server 2012,以下是我的实体。应用程序可以有多个帐户。有一个帐户表和一个应用程序表。

实体
1.申请书 2.账户

的ViewModels
1. ApplicationListViewModel
2. ApplicationViewModel
3. AccountListViewModel
4. AccountViewModel

在我的用户控件中,我正在尝试执行以下操作:
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属性的屏幕截图。

enter image description here

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

现在,我只在一个SelectedApplication中创建了SelectedAccountAccountsViewModel属性。删除了所有复杂的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概念。我试图建立模块化的所有东西,最后我搞砸了。

2 个答案:

答案 0 :(得分:3)

我怀疑您的问题与您每次致电ObservableCollection的设置者时返回 AccountLVM这一事实有关,并且您没有提出PropertyChange 1}}通知,因此任何现有绑定都不会更新

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

它正在创建一个 new AccountLVM,它没有设置SelectedAccount属性。

您根本没有看到现有的DataGrid.SelectedItem更改,因为它仍然绑定到 AccountLVM,因为PropertyChange时没有_accountLVM通知1}}已更改,因此绑定无法更新。

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

  • 除非您还为该属性的公共版本引发PropertyChange通知,否则请勿更改该属性的私有版本。这适用于您的构造函数和GetXxxxx()方法,例如GetAccounts()

  • 不要从getter返回方法调用。而是使用方法调用设置值,如果它为null,则返回私有属性。

    public AccountListViewModel AccountLVM
    {
        get
        {
            if (_accountLVM == null)
                GetAccounts(); // or _accountLVM = GetAccountLVM();
    
            return _accountLVM;
        }
        set { ... }
    }
    
  • DataContext设置在如此多的控件中真的很混乱。 DataContext是您的用户界面背后的数据层,如果您的用户界面只是简单地反映了数据层,而且必须遍布整个地方来获取您的数据,那么数据层就很难跟进。

  • 如果必须对当前数据上下文以外的其他内容进行绑定,请在立即更改DataContext之前尝试使用其他绑定属性specify a different binding 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的新用户或者很难理解它,我建议您在我的博客上阅读this article,以便更好地了解它是什么以及它是如何运作的。

答案 1 :(得分:2)

这个Binding方法的一个主要问题是,只有在您的案例SelectedAccount中的最后一个属性值发生更改时,才会更新该值。 BindingExpression不会监视其他级别,因此,例如SelectedApplication.AccountLVM已更改,DataContext不会注意到SelectedAccount中的差异,因为绑定仍在“正在观看”旧参考,并且您正在修改虚拟机中的其他参考。

所以我认为在应用程序开始时SelectedApplication为空,Binding的{​​{1}}没有注意到它发生了变化。嗯,我想到了另一个绑定解决方案,但我找不到一个。因此,我建议您创建一个附加属性,以便在ComboBox课程中反映SelectedAccount