簡體   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