繁体   English   中英

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

[英]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创建了SelectedApplicationSelectedAccount属性。 删除了所有复杂的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.AccountLVMDataContext将不会注意到SelectedAccount的差异,因为绑定仍在“正在观察”旧引用,并且您正在修改VM中的另一个引用。

所以我认为在应用程序的开始时, SelectedApplication为null并且ComboBoxBinding没有注意到它的变化。 嗯,我想到了另一个绑定解决方案,但我找不到一个。 所以我建议你创建一个额外的属性来反映ApplicationListViewModel类中的SelectedAccount

暂无
暂无

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

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