WPF 将数据绑定到 DataGrids 的 TabControl
WPF bind data to TabControl of DataGrids
我正在尝试创建一个 UI,其中我有一个 TabControl,并且在每个选项卡中都有一个 DataGrid。我想动态 add/remove 选项卡和 rows/columns 到 DataGrid。这是一个代码示例:
Test.xaml
<StackPanel>
<Button x:Name="Button" Content="Add tab" Click="Button_Click"/>
<Controls:MetroAnimatedTabControl x:Name="TabControl"
TabStripPlacement="Left"
DisplayMemberPath="TabName">
<TabControl.ContentTemplate>
<DataTemplate>
<DataGrid AutoGenerateColumns="True" DataContext="{Binding Context}" />
</DataTemplate>
</TabControl.ContentTemplate>
</Controls:MetroAnimatedTabControl>
</StackPanel>
和代码隐藏
Test.xaml.cs
public class Tab
{
public string TabName { get; set; }
public DataTable Content { get; set; }
public Tab(string name, DataTable content)
{
TabName = name;
Content = content;
}
public Tab(string name, List<string[]> content)
{
Content = new DataTable();
foreach (var item in content){
Content.Columns.Add(item[0], typeof(string));
}
DataRow row = Content.NewRow();
foreach (var item in content)
{
row[item[0]] = item[1];
}
Content.Rows.Add(row);
TabName = name;
}
}
public partial class Test: UserControl
{
ObservableCollection<Tab> clsTabs = new ObservableCollection<Tab>();
public Test()
{
InitializeComponent();
DataTable table = new DataTable();
clsTabs.Add(new Tab("Animals", new List<string[]>() { new string[] { "Name", "Tiger" }, new string[] { "Tail", "Yes" } }));
clsTabs.Add(new Tab("Vegetables", new List<string[]>() { new string[] { "Name", "Tomato" }, new string[] { "Color", "Red" }, new string[] { "Taste", "Good" } }));
clsTabs.Add(new Tab("Cars", new List<string[]>() { new string[] { "Name", "Tesla" } }));
TabControl.DataContext = clsTabs;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
clsTabs.Add(new Tab("New", new List<string[]>() { new string[] { "Name", "Something" }, new string[] { "Detail", "No" } }));
}
}
它可以编译,但是当您 运行 应用程序时 window 中没有显示任何内容。数据绑定很可能是错误的(尤其是 DataGrid,因为我不知道如何使用像我这样的 class 来做到这一点)。
如果代码不清楚,在我的 Tab
class 选项卡名称中有 TabName
属性,Content
DataTable应该是对应DataGrid中的数据源。我想以某种方式将它们绑定到 xaml,如果实例被修改,UI 也会更新。
是否可以这样做,或者我需要采取不同的方法吗?
根本问题是这些为多个项目生成内容的控件应该使用 ItemsSource
属性 而不是 DataContext
.
填充
行
TabControl.DataContext = clsTabs;
应该分配给 TabControl.ItemsSource
此时您将在“输出”窗格中看到
System.Windows.Data Error: 40 : BindingExpression path error: 'Context' property not found on 'object' ''Tab' (HashCode=55467050)'. BindingExpression:Path=Context; DataItem='Tab' (HashCode=55467050); target element is 'DataGrid' (Name=''); target property is 'DataContext' (type 'Object')
行
<DataGrid AutoGenerateColumns="True" DataContext="{Binding Context}" />
有之前的问题和错误的属性名字。它应该是
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Content}" />
然后你应该很好。
DataContext
is the context of the bindings in the current scope. The TabControl
is a ItemsControl
which has an ItemsSource
that requires a IEnumerable
(IEnumerable<Tab>
in this case). You should introduce a view model which serves as the DataContext
of the UserControl
and in this case exposes the source collection ObservableCollection<Tab>
which the TabControl
is bound to. The view model generally will host all the data that the view can bind to. The view model usually implements INotifyPropertyChanged
界面使 UI 控件在绑定源更改时自动更新。
Tab.cs(选项卡控件将绑定到的数据模型):
public class Tab
{
public string TabName { get; set; }
public DataTable Content { get; set; }
public Tab(string name, DataTable content)
{
TabName = name;
Content = content;
}
public Tab(string name, List<string[]> content)
{
Content = new DataTable();
foreach (var item in content){
Content.Columns.Add(item[0], typeof(string));
}
DataRow row = Content.NewRow();
foreach (var item in content)
{
row[item[0]] = item[1];
}
Content.Rows.Add(row);
TabName = name;
}
}
ViewModel.cs(UserControl
的 DataContext
将 Tab
集合公开为绑定上下文:
class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.ClsTabs = new ObservableCollection<Tab>();
ClsTabs.Add(new Tab("Animals", new List<string[]>() { new string[] { "Name", "Tiger" }, new string[] { "Tail", "Yes" } }));
ClsTabs.Add(new Tab("Vegetables", new List<string[]>() { new string[] { "Name", "Tomato" }, new string[] { "Color", "Red" }, new string[] { "Taste", "Good" } }));
ClsTabs.Add(new Tab("Cars", new List<string[]>() { new string[] { "Name", "Tesla" } }));
}
private ObservableCollection<Tab> clsTabs;
public ObservableCollection<Tab> ClsTabs
{
get => this.clsTabs;
set
{
if (Equals(value, this.clsTabs)) return;
this.clsTabs = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Test.xaml.cs:
public partial class Test: UserControl
{
public Test()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
(this.DataContext as ViewModel)?.ClsTabs.Add(new Tab("New", new List<string[]>() { new string[] { "Name", "Something" }, new string[] { "Detail", "No" } }));
}
}
Test.xaml
<UserControl x:Class="WpfTestRange.Main.Test">
<!-- Set the DataContext of the Test control to an instance of ViewModel -->
<UserControl.DataContext>
<local:ViewModel />
</UserControl.DataContext>
<Grid>
<StackPanel>
<Button x:Name="Button"
Content="Add tab"
Click="Button_Click" />
<MetroAnimatedTabControl x:Name="TabControl"
ItemsSource="{Binding ClsTabs}"
TabStripPlacement="Left"
DisplayMemberPath="TabName">
<TabControl.ContentTemplate>
<DataTemplate DataType="local:Tab">
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
</Grid>
</UserControl>
我建议你看看Microsoft Docs: Basic Data Binding Concepts, and MVVM (Microsoft Docs: The Model-View-ViewModel Pattern (mentions Xamarin.Forms but everything applies to WPF as well), MVVM Pattern Made Simple)
我正在尝试创建一个 UI,其中我有一个 TabControl,并且在每个选项卡中都有一个 DataGrid。我想动态 add/remove 选项卡和 rows/columns 到 DataGrid。这是一个代码示例:
Test.xaml
<StackPanel>
<Button x:Name="Button" Content="Add tab" Click="Button_Click"/>
<Controls:MetroAnimatedTabControl x:Name="TabControl"
TabStripPlacement="Left"
DisplayMemberPath="TabName">
<TabControl.ContentTemplate>
<DataTemplate>
<DataGrid AutoGenerateColumns="True" DataContext="{Binding Context}" />
</DataTemplate>
</TabControl.ContentTemplate>
</Controls:MetroAnimatedTabControl>
</StackPanel>
和代码隐藏
Test.xaml.cs
public class Tab
{
public string TabName { get; set; }
public DataTable Content { get; set; }
public Tab(string name, DataTable content)
{
TabName = name;
Content = content;
}
public Tab(string name, List<string[]> content)
{
Content = new DataTable();
foreach (var item in content){
Content.Columns.Add(item[0], typeof(string));
}
DataRow row = Content.NewRow();
foreach (var item in content)
{
row[item[0]] = item[1];
}
Content.Rows.Add(row);
TabName = name;
}
}
public partial class Test: UserControl
{
ObservableCollection<Tab> clsTabs = new ObservableCollection<Tab>();
public Test()
{
InitializeComponent();
DataTable table = new DataTable();
clsTabs.Add(new Tab("Animals", new List<string[]>() { new string[] { "Name", "Tiger" }, new string[] { "Tail", "Yes" } }));
clsTabs.Add(new Tab("Vegetables", new List<string[]>() { new string[] { "Name", "Tomato" }, new string[] { "Color", "Red" }, new string[] { "Taste", "Good" } }));
clsTabs.Add(new Tab("Cars", new List<string[]>() { new string[] { "Name", "Tesla" } }));
TabControl.DataContext = clsTabs;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
clsTabs.Add(new Tab("New", new List<string[]>() { new string[] { "Name", "Something" }, new string[] { "Detail", "No" } }));
}
}
它可以编译,但是当您 运行 应用程序时 window 中没有显示任何内容。数据绑定很可能是错误的(尤其是 DataGrid,因为我不知道如何使用像我这样的 class 来做到这一点)。
如果代码不清楚,在我的 Tab
class 选项卡名称中有 TabName
属性,Content
DataTable应该是对应DataGrid中的数据源。我想以某种方式将它们绑定到 xaml,如果实例被修改,UI 也会更新。
是否可以这样做,或者我需要采取不同的方法吗?
根本问题是这些为多个项目生成内容的控件应该使用 ItemsSource
属性 而不是 DataContext
.
行
TabControl.DataContext = clsTabs;
应该分配给 TabControl.ItemsSource
此时您将在“输出”窗格中看到
System.Windows.Data Error: 40 : BindingExpression path error: 'Context' property not found on 'object' ''Tab' (HashCode=55467050)'. BindingExpression:Path=Context; DataItem='Tab' (HashCode=55467050); target element is 'DataGrid' (Name=''); target property is 'DataContext' (type 'Object')
行
<DataGrid AutoGenerateColumns="True" DataContext="{Binding Context}" />
有之前的问题和错误的属性名字。它应该是
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Content}" />
然后你应该很好。
DataContext
is the context of the bindings in the current scope. The TabControl
is a ItemsControl
which has an ItemsSource
that requires a IEnumerable
(IEnumerable<Tab>
in this case). You should introduce a view model which serves as the DataContext
of the UserControl
and in this case exposes the source collection ObservableCollection<Tab>
which the TabControl
is bound to. The view model generally will host all the data that the view can bind to. The view model usually implements INotifyPropertyChanged
界面使 UI 控件在绑定源更改时自动更新。
Tab.cs(选项卡控件将绑定到的数据模型):
public class Tab
{
public string TabName { get; set; }
public DataTable Content { get; set; }
public Tab(string name, DataTable content)
{
TabName = name;
Content = content;
}
public Tab(string name, List<string[]> content)
{
Content = new DataTable();
foreach (var item in content){
Content.Columns.Add(item[0], typeof(string));
}
DataRow row = Content.NewRow();
foreach (var item in content)
{
row[item[0]] = item[1];
}
Content.Rows.Add(row);
TabName = name;
}
}
ViewModel.cs(UserControl
的 DataContext
将 Tab
集合公开为绑定上下文:
class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.ClsTabs = new ObservableCollection<Tab>();
ClsTabs.Add(new Tab("Animals", new List<string[]>() { new string[] { "Name", "Tiger" }, new string[] { "Tail", "Yes" } }));
ClsTabs.Add(new Tab("Vegetables", new List<string[]>() { new string[] { "Name", "Tomato" }, new string[] { "Color", "Red" }, new string[] { "Taste", "Good" } }));
ClsTabs.Add(new Tab("Cars", new List<string[]>() { new string[] { "Name", "Tesla" } }));
}
private ObservableCollection<Tab> clsTabs;
public ObservableCollection<Tab> ClsTabs
{
get => this.clsTabs;
set
{
if (Equals(value, this.clsTabs)) return;
this.clsTabs = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Test.xaml.cs:
public partial class Test: UserControl
{
public Test()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
(this.DataContext as ViewModel)?.ClsTabs.Add(new Tab("New", new List<string[]>() { new string[] { "Name", "Something" }, new string[] { "Detail", "No" } }));
}
}
Test.xaml
<UserControl x:Class="WpfTestRange.Main.Test">
<!-- Set the DataContext of the Test control to an instance of ViewModel -->
<UserControl.DataContext>
<local:ViewModel />
</UserControl.DataContext>
<Grid>
<StackPanel>
<Button x:Name="Button"
Content="Add tab"
Click="Button_Click" />
<MetroAnimatedTabControl x:Name="TabControl"
ItemsSource="{Binding ClsTabs}"
TabStripPlacement="Left"
DisplayMemberPath="TabName">
<TabControl.ContentTemplate>
<DataTemplate DataType="local:Tab">
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
</Grid>
</UserControl>
我建议你看看Microsoft Docs: Basic Data Binding Concepts, and MVVM (Microsoft Docs: The Model-View-ViewModel Pattern (mentions Xamarin.Forms but everything applies to WPF as well), MVVM Pattern Made Simple)