在 N 层应用程序中使用 WPF 和 WCF 在数据网格中显示数据库数据
Displaying Database Data in a Datagrid using WPF and WCF in an N-Tier Application
我正在尝试使用 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;
}
只选一个。
我正在尝试使用 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;
}
只选一个。