4

我无法弄清楚为什么我在 WPF 中的第三个嵌套数据绑定不起作用。我正在使用实体框架和 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>

应用程序列表视图模型

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

帐户视图模型。为简单起见,我在 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:
根据下面评论中的建议,我尝试了 snoop,并且能够看到标题文本框行以红色突出显示。我正在尝试更改绑定路径属性和数据上下文,但仍然无法正常工作。当我尝试单击“Delve 绑定表达式”时,它给了我未处理的异常。我不知道这意味着什么,如果它来自 Snoop。

Edit3:
我为 StackPanel 的 Account Details 部分和文本框的 text 属性截取了 DataContext 属性的屏幕截图。

在此处输入图像描述

解决方案:
根据以下建议,我对我的解决方案进行了以下更改,并使其更加简单。我让它变得不必要的复杂。
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 端

<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 概念。我试图构建所有模块化的东西,最后我把它搞砸了。

4

2 回答 2

4

我怀疑您的问题与您每次调用 setter 时都返回一个 的事实有关,并且您没有提出通知,因此任何现有绑定都不会更新ObservableCollectionAccountLVMPropertyChange

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的,因为更改时没有PropertyChange发出通知_accountLVM,因此绑定不知道要更新。

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

  • 不要更改属性的私有版本,除非您还针对属性的公共版本发出 PropertyChange 通知。这适用于您的构造函数和您的GetXxxxx()方法,例如GetAccounts().

  • 不要从你的 getter 返回方法调用。相反,如果它为空,则使用您的方法调用设置值,然后返回私有属性。

    public AccountListViewModel AccountLVM
    {
        get
        {
            if (_accountLVM == null)
                GetAccounts(); // or _accountLVM = GetAccountLVM();
    
            return _accountLVM;
        }
        set { ... }
    }
    
  • DataContext在这么多控件中设置集合确实令人困惑。这DataContext是你的 UI 背后的数据层,如果你的 UI 只是反映数据层,这是最简单的,并且必须到处去获取你的数据使得数据层很难遵循。

  • 如果您必须对当前数据上下文以外的内容进行绑定,请尝试使用其他绑定属性来指定不同的绑定Source,然后再立即更改DataContext. 下面是一个使用该ElementName属性设置绑定源的示例:

    <TextBox x:Name="txtTitle" ...
             Text="{Binding ElementName=dtgAccounts, Path=SelectedItem.Title}" />
    
  • in 继承,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或难以理解它,我建议您阅读我博客上的这篇文章,以更好地了解它是什么以及它是如何工作的。

于 2013-03-19T17:06:50.370 回答
2

这种Binding方法的一个主要问题是,只有在最后一个属性值(在您的情况下SelectedAccount)发生更改时,才会更新该值。其他级别不会被 监视BindingExpression,因此如果SelectedApplication.AccountLVM更改了例如,DataContext将不会注意到差异,SelectedAccount因为绑定仍在旧引用上“监视”,并且您正在修改 VM 中的另一个引用。

所以我认为在应用程序的开头SelectedApplication是 null 并且没有注意到它发生了变化BindingComboBox嗯,我想过另一种绑定解决方案,但我找不到。所以我建议你创建一个额外的属性来反映SelectedAccount你的ApplicationListViewModel班级。

于 2013-03-19T07:56:45.850 回答