简体   繁体   English

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

[英]Nested Data Binding using MVVM in WPF not working

I am not able to figure out why my third Nested DataBinding in WPF is not working. 我无法弄清楚为什么WPF中的第三个嵌套数据绑定无法正常工作。 I am using Entity Framework and Sql Server 2012 and following are my entities. 我正在使用Entity Framework和Sql Server 2012,以下是我的实体。 An Application can have more than one accounts. 应用程序可以有多个帐户。 There is an Accounts Table and an Applications Table. 有一个帐户表和一个应用程序表。

ENTITIES 实体
1. Applications 1.申请
2. Accounts 2.账目

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

In my usercontrol I am trying to do following: 在我的usercontrol中,我尝试执行以下操作:
1. Use combobox to select an application using ApplicationListViewModel ( Working ) 1.使用组合框使用ApplicationListViewModel选择应用程序( 工作
2. Upon selected application display all accounts in datagrid ( Working ) 2.在选定的应用程序中显示数据网格中的所有帐户( 工作
3. Upon selected account display details information about a particular account.( Does not show details of the selected account ) 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 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 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 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. AccountViewModel。 I am eliminating all other initialization logic aside in viewmodel for simplicity. 为简单起见,我在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: EDIT1:
When I data bind to view the details of the SelectedAccount with textbox it doesn't show any text. 当我使用数据绑定来查看带有文本框的SelectedAccount的详细信息时,它不会显示任何文本。
1. Able to databind to ApplicationListViewModel to Combobox. 1.能够将ApplicationListViewModel数据绑定到Combobox。
2. Successfully Bind to view AccountList based upon SelectedApplication 2.成功绑定以基于SelectedApplication查看AccountList
3. Unable to Bind to SelectedAcount in the AccountListViewModel. 3.无法绑定到AccountListViewModel中的SelectedAcount。

I think in the following line it doesn't show any details about the selected account. 我认为在以下行中它没有显示有关所选帐户的任何详细信息。 I have checked all databinding syntax. 我检查了所有数据绑定语法。 In the properties I am able to view appropriate DataContext and bind to the properties. 在属性中,我能够查看适当的DataContext并绑定到属性。 But it doesn't show any text. 但它没有显示任何文字。 When I select each individual record in the DataGrid I am able to debug the call and select the object but somehow that object is not being shown in the textbox at the very end. 当我在DataGrid中选择每个单独的记录时,我能够调试该调用并选择该对象,但不知何故该对象未在最后的文本框中显示。

DataContext="{Binding SelectedApplication.AccountLVM.SelectedAccount}"

Edit2: EDIT2:
Based upon the suggestion in the comment below I tried snoop and was able to see the title textbox row highlighted in red color. 根据下面评论中的建议,我尝试了窥探,并能够看到标题文本框行以红色突出显示。 I am trying to change the binding Path property and datacontext but still not working. 我试图更改绑定路径属性和datacontext但仍然无法正常工作。 When I tried to click on the "Delve Binding Expression" it gave me unhandled exception. 当我试图点击“Delve Binding Expression”时,它给了我未处理的异常。 I don't know what that means if as it came from Snoop. 如果它来自Snoop,我不知道这意味着什么。

Edit3: EDIT3:
I have taken screenshots of DataContext Property for the StackPanel for the Account Details section and the text property of the textbox. 我已经为“帐户详细信息”部分的StackPanel和文本框的文本属性截取了DataContext属性的屏幕截图。

在此输入图像描述

Solution: 解:
Based upon suggestions below I have made following changes to my solution and made it way more simple. 根据以下建议,我对我的解决方案进行了以下更改,并使其变得更加简单。 I made it unnecessarily complex. 我做得太不必要了。
1. AccountsViewModel 1. AccountsViewModel
2. AccountViewModel 2. AccountViewModel
3. ApplicationViewModel 3. ApplicationViewModel

Now I have created properties as SelectedApplication , SelectedAccount all in just one AccountsViewModel . 现在我只在一个AccountsViewModel创建了SelectedApplicationSelectedAccount属性。 Removed all complex DataContext syntax and now there is just one DataContext in the xaml page. 删除了所有复杂的DataContext语法,现在xaml页面中只有一个DataContext。

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;
        }
        //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 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 didn't understood MVVM concept properly. 我没有正确理解MVVM概念。 I tried to build everything modular and in the end I screwed it up. 我试图建立模块化的所有东西,最后我搞砸了。

I suspect your problem is related to the fact you are returning a new ObservableCollection every time you call the setter for AccountLVM , and you are not raising your PropertyChange notification, so any existing bindings do not get updated 我怀疑你的问题与每次调用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;
}

I find your bindings very confusing and hard to follow, however I think whenever this gets evaluated 我发现你的绑定非常令人困惑并且很难遵循,但是我认为无论何时进行评估

DataContext="{Binding SelectedApplication.AccountLVM.SelectedAccount}"

it is creating a new AccountLVM , which does not have the SelectedAccount property set. 它正在创建一个新的 AccountLVM ,它没有设置SelectedAccount属性。

You don't see the existing DataGrid.SelectedItem change at all because it's still bound to the old AccountLVM as no PropertyChange notification got raised when _accountLVM changed, so the binding doesn't know to update. 您根本看不到现有的DataGrid.SelectedItem更改,因为它仍然绑定到旧的 AccountLVM因为_accountLVM更改时没有引发PropertyChange通知,因此绑定不知道更新。

But some other miscellaneous related to your code: 但是其他一些与您的代码相关的杂项:

  • Don't change the private version of the property unless you also raise the PropertyChange notification for the public version of the property. 除非您还为属性的公共版本引发PropertyChange通知,否则请勿更改属性的私有版本。 This applies to both your constructors and your GetXxxxx() methods like GetAccounts() . 这适用于构造函数和GetXxxxx()GetAccounts()方法。

  • Don't return a method call from your getter. 不要从getter返回方法调用。 Instead set the value using your method call if it's null, and return the private property afterwards. 而是使用方法调用设置值,如果它为null,则返回私有属性。

     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. DataContext设置在如此多的控件中真的很令人困惑。 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. DataContext是UI背后的数据层,如果您的UI只是简单地反映了数据层,并且必须遍布整个地方来获取数据,那么数据层就很难实现。

  • 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 . 如果必须对当前数据上下文以外的其他内容进行绑定,请在立即更改DataContext之前尝试使用其他绑定属性指定其他绑定Source Here's an example using the ElementName property to set the binding source: 这是使用ElementName属性设置绑定源的示例:

     <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 }" 继承了DataContext ,因此您不需要编写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. 您可能需要考虑重写您的父ViewModel,以便您可以像这样设置XAML,而无需所有额外的DataContext绑定或3部分嵌套属性。

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

If you're new to the DataContext or struggling to understand it, I'd recommend reading this article on my blog to get a better understanding of what it is and how it works. 如果您是DataContext新手或者正在努力理解它,我建议您在我的博客上阅读这篇文章 ,以便更好地了解它是什么以及它是如何工作的。

Well one major problem with this Binding method is, that the value is only updated, when the last property value, in your case SelectedAccount , is changed. 这个Binding方法的一个主要问题是,只有在您的案例SelectedAccount的最后一个属性值发生更改时,才会更新该值。 The other levels are not watched by the BindingExpression , so if eg SelectedApplication.AccountLVM is changed the DataContext will not notice a difference in SelectedAccount because the binding is still 'watching' on the old reference and you're modifying another reference in your VM. BindingExpression不会监视其他级别,因此如果更改了SelectedApplication.AccountLVMDataContext将不会注意到SelectedAccount的差异,因为绑定仍在“正在观察”旧引用,并且您正在修改VM中的另一个引用。

So I think at the start of the application SelectedApplication is null and the Binding of the ComboBox doesn't notice that it changes. 所以我认为在应用程序的开始时, SelectedApplication为null并且ComboBoxBinding没有注意到它的变化。 Hmm, I thought about another binding solution, but I couldn't found one. 嗯,我想到了另一个绑定解决方案,但我找不到一个。 So I suggest, that you create an additional property for reflecting SelectedAccount in your ApplicationListViewModel class. 所以我建议你创建一个额外的属性来反映ApplicationListViewModel类中的SelectedAccount

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM