【问题标题】:WPF bind data to TabControl of DataGridsWPF 将数据绑定到 DataGrids 的 TabControl
【发布时间】:2019-07-28 21:46:21
【问题描述】:

我正在尝试创建一个 UI,其中有一个 TabControl,并且在每个选项卡中都有一个 DataGrid。我想向 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" } }));
    }
}

它可以编译,但是当您运行应用程序时,窗口中没有显示任何内容。数据绑定很可能是错误的(尤其是对 DataGrid,因为我不知道如何使用像我这样的类来做到这一点)。

如果从代码中不清楚,在我的Tab 类中,我有TabName 属性作为选项卡名称,Content DataTable 应该是相应DataGrid 中的数据源。而且我想以某种方式将它们绑定到 xaml,如果实例被修改,UI 也会更新。

是否可以这样做,还是我需要采取不同的方法?

【问题讨论】:

  • 尝试使用 更新数据模板定义并设置 DataGrid 的 ItemsSource 属性
  • @NthDeveloper 试过了,但没有运气,仍然只显示一个空的 DataGrid

标签: c# wpf data-binding datagrid mahapps.metro


【解决方案1】:

DataContext 是当前范围内绑定的上下文。 TabControl 是一个 ItemsControl,它有一个需要 IEnumerableItemsSource(在这种情况下为 IEnumerable&lt;Tab&gt;)。您应该引入一个视图模型,它充当UserControlDataContext,在这种情况下公开TabControl 绑定到的源集合ObservableCollection&lt;Tab&gt;。视图模型通常将托管视图可以绑定到的所有数据。视图模型通常实现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(UserControlDataContextTab 集合公开为绑定的上下文):

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 和 MVVM(Microsoft Docs: The Model-View-ViewModel Pattern(提到 Xamarin.Forms,但所有内容也适用于 WPF),MVVM Pattern Made Simple

【讨论】:

  • 完美,谢谢!你能解释一下 ViewModel 中[NotifyPropertyChangedInvocator] 的用途是什么吗?我将其注释掉并且代码有效,那么拥有它有什么优势,我如何引用它或我需要如何/在哪里实现它?
  • 我有一个后续问题,当我向数据网格添加新行时,它会自动更新,但如果我添加新列,它只会在我切换到另一个选项卡然后返回时更新 UI。所以我假设它只在需要再次加载整个表时更新列。有没有办法让这个更新即时? (最坏情况触发表再次加载)
  • 我有点困惑。假设我在开始时有 2 列和 1 行。我添加了一个新列,为了更新 UI,我还需要添加一个新行(所以现在我有 2 行),其中只有 column3 有数据?还是需要将 column3 数据添加到现有行?
  • 是的,就是这样。当然,一个例子会很棒。所以最后我想要管理房屋费用的东西,每个选项卡是一年,并且在每个年选项卡中都有一个 DataGrid ,其中标题是不同的费用,行是月份(我刚刚在示例中使用了随机名称)。并有用于添加新行(月)和另一个用于添加列(费用类型)的按钮。我认为您的第一个解决方案是最好的,但如果您从我的场景中获得一些见解,任何其他建议都会被采纳
  • 不一定是DataTable,你会使用什么其他结构?好的,我试着举个例子。假设我的房子有一些费用,比如:暖气、电、租金。所以DataGrid 中的列标题是“月”、“供暖”、“电”、“租金”。现在我有今年每个月的 7 行数据,例如:{“July”、“3€”、“4€”、“5€”}。假设我在家里安装了互联网,所以从现在开始我需要一个新的费用类型(DataGrid 的新列)。这就是添加列的含义
【解决方案2】:

根本问题是这些为多个项目生成内容的控件应该使用ItemsSource 属性填充,而不是DataContext

线 TabControl.DataContext = clsTabs; 应该分配给TabControl.ItemsSource

此时您将在“输出”窗格中看到

System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“选项卡”(HashCode=55467050)上找不到“上下文”属性。绑定表达式:路径=上下文; DataItem='Tab' (HashCode=55467050);目标元素是'DataGrid'(名称='');目标属性是“DataContext”(类型“对象”)

线 &lt;DataGrid AutoGenerateColumns="True" DataContext="{Binding Context}" /&gt; 有上一个问题错误的属性名称。它应该是 &lt;DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Content}" /&gt; 然后你应该很好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-21
    • 1970-01-01
    • 2011-02-09
    • 1970-01-01
    • 2021-10-27
    相关资源
    最近更新 更多