在 N 层应用程序中使用 WPF 和 WCF 在数据网格中显示数据库数据

Displaying Database Data in a Datagrid using WPF and WCF in an N-Tier Application

已完成以下教程:https://docs.microsoft.com/en-us/visualstudio/data-tools/walkthrough-creating-an-n-tier-data-application?view=vs-2019

我正在尝试使用 WPF 而不是 Windows Forms 来制作相同的教程,但我终究无法让它在 Datagrid 中显示数据库数据。除了 PresentationTier 之外,我的所有代码都是相同的(但使用我自己的数据库数据)。我的 .xaml 代码是(主要是从“数据源”选项卡中拖动 table 的默认设置):

<Page
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:DataEntityTier="clr-namespace:DataEntityTier;assembly=DataEntityTier" 
      x:Class="PresentationTier.ViewProducts"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800"
      Title="Worker-ViewProducts" Background="White" Loaded="Page_Loaded">

    <Page.Resources>
        <DataEntityTier:WMSDataSet x:Key="wMSDataSet"/>
        <CollectionViewSource x:Key="productsViewSource" Source="{Binding products, Source={StaticResource wMSDataSet}}"/>
        <CollectionViewSource x:Key="warehousesViewSource" Source="{Binding warehouses, Source={StaticResource wMSDataSet}}"/>
    </Page.Resources>

    <Grid Margin="10" DataContext="{StaticResource productsViewSource}">
        <DataGrid x:Name="productsDataGrid" AutoGenerateColumns="False" EnableRowVirtualization="True" ItemsSource="{Binding Source={StaticResource productsViewSource}}" Margin="165,230,215,0" RowDetailsVisibilityMode="VisibleWhenSelected">
            <DataGrid.Columns>
                <DataGridTextColumn x:Name="product_idColumn" Binding="{Binding product_id}" Header="product id" IsReadOnly="True" Width="SizeToHeader"/>
                <DataGridTextColumn x:Name="account_idColumn" Binding="{Binding account_id}" Header="account id" Width="SizeToHeader"/>
                <DataGridTextColumn x:Name="titleColumn" Binding="{Binding title}" Header="title" Width="SizeToHeader"/>
                <DataGridTextColumn x:Name="skuColumn" Binding="{Binding sku}" Header="sku" Width="SizeToHeader"/>
            </DataGrid.Columns>
        </DataGrid>
        <DataGrid x:Name="warehousesDataGrid" AutoGenerateColumns="False" EnableRowVirtualization="True" ItemsSource="{Binding Source={StaticResource warehousesViewSource}}" Margin="165,0,215,230" RowDetailsVisibilityMode="VisibleWhenSelected">
            <DataGrid.Columns>
                <DataGridTextColumn x:Name="warehouse_idColumn" Binding="{Binding warehouse_id}" Header="warehouse id" IsReadOnly="True" Width="SizeToHeader"/>
                <DataGridTextColumn x:Name="nameColumn" Binding="{Binding name}" Header="name" Width="SizeToHeader"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Page>

我的 xaml.cs 代码是:

using DataEntityTier;
using System.Windows;
using System.Windows.Controls;

namespace PresentationTier
{
    public partial class ViewProducts : Page
    {
        WMSDataSet wMSDataSet;

        public ViewProducts()
        {
            InitializeComponent();
            Loaded += Page_Loaded;
        }

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            wMSDataSet = new WMSDataSet();
            ServiceReference1.Service1Client DataSvc = new ServiceReference1.Service1Client();
            wMSDataSet.products.Merge(DataSvc.GetProducts());
            wMSDataSet.warehouses.Merge(DataSvc.GetWarehouses());
        }
    }
}

您在错误的 WMSDataSet 实例上操作。

您的数据绑定引用了页面 ResourceDictionary 中定义的实例。但是您实际上是在 Loaded 事件处理程序中初始化第二个实例。

解决方案是在代码隐藏中实例化 WMSDataSet,然后将其添加到 ResourceDictionary(或将其分配给可绑定的 属性)或检索 XAML实例并初始化它。
第二种解决方案将要求 WMSDataSet 对象实现 INotifyPropertyChanged 以使数据绑定通知 属性 更改以更新绑定目标。我建议通过 DependencyProperty:

公开 WMSDataSet

ViewProducts.xaml.cs

public partial class ViewProducts : Page
{
  public static readonly DependencyProperty WmsDataSourceProperty = DependencyProperty.Register(
    "WmsDataSource",
    typeof(WMSDataSet),
    typeof(ViewProducts),
    new PropertyMetadata(default(WMSDataSet)));

  public WMSDataSet WmsDataSource
  {
    get => (WMSDataSet) GetValue(ViewProducts.WmsDataSourceProperty);
    set => SetValue(ViewProducts.WmsDataSourceProperty, value);
  }


  public ViewProducts()
  {
    InitializeComponent();
    Loaded += Page_Loaded;
  }

  private void Page_Loaded(object sender, RoutedEventArgs e)
  {
    wmsDataSet = new WMSDataSet();
    ServiceReference1.Service1Client dataSvc = new ServiceReference1.Service1Client();
    wmsDataSet.products.Merge(dataSvc.GetProducts());
    wmsDataSet.warehouses.Merge(dataSvc.GetWarehouses());
  
    this.WmsDataSource = wmsDataSet;
  }
}

ViewProducts.xaml

<Page>
  <Page.Resources>
    <CollectionViewSource x:Key="ProductsViewSource" Source="{Binding RelativeSource={RelativeSource AncestorType=local:ViewProducts}, Path=WmsDataSource.products}" />
    <CollectionViewSource x:Key="WarehousesViewSource" Source="{Binding RelativeSource={RelativeSource AncestorType=local:ViewProducts}, Path=WmsDataSource.warehouses}" />
  </Page.Resources>

  <!-- When you set the DataContext to 'ViewProducts.WmsDataSource', you can directly bind to it
       using {Binding} (without specifying the Binding.Source) -->
  <Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=local:ViewProducts}, Path=WmsDataSource}">
    <DataGrid ItemsSource="{Binding products}">
    </DataGrid>

    <DataGrid ItemsSource="{Binding warehouses}">
    </DataGrid>
  </Grid>
</Page>

备注

您的代码中有一些错误。你做了很多多余的事情,因为你做了两次,后者覆盖了前一个声明,引入了错误或意外行为(例如空白控件):

当然已经提到了WMSDataSet的两个实例的用法。
在 XAML:

中实例化
<DataEntityTier:WMSDataSet x:Key="wMSDataSet"/>

和代码隐藏:

this.wMSDataSet = new WMSDataSet();

那么你正在设置父元素的 DataContext 只是为了在你的绑定表达式中忽略它:

<Grid DataContext="{StaticResource productsViewSource}">
  <DataGrid ItemsSource="{Binding Source={StaticResource productsViewSource}}">

而不是:

<Grid DataContext="{StaticResource productsViewSource}">
  <DataGrid ItemsSource="{Binding}">

由于 hte common parent Grid 的内容(两个 DataGrid 元素)不共享 相同 数据上下文,将它们的 DataContext 设置为共同来源更容易混淆,然后更有帮助。而是选择定义以下版本:

<Grid>
  <DataGrid ItemsSource="{Binding Source={StaticResource productsViewSource}}" />
  <DataGrid ItemsSource="{Binding Source={StaticResource warehousesViewSource}}" />
</Grid>

或者在我的解决方案的重构版本中:

<Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=local:ViewProducts}, Path=WmsDataSource}">
  <DataGrid ItemsSource="{Binding products}" />
  <DataGrid ItemsSource="{Binding warehouses}" />
</Grid>

您还订阅了 Loaded 事件两次,这导致事件处理程序也被调用两次。您首先在 XAML 中订阅了 PageLoaded:

<Page Loaded="Page_Loaded">

在代码隐藏中:

public ViewProducts()
{
  InitializeComponent();
  Loaded += Page_Loaded;
}

只选一个。