【问题标题】:WPF: Binding Data Grid Header WidthWPF:绑定数据网格标题宽度
【发布时间】:2017-06-11 06:22:54
【问题描述】:

我正在尝试将一个数据网格的标题宽度绑定到另一个数据网格的标题。

所以,我为父数据网格的每一行添加了一个数据网格。现在,我正在尝试使用父标题的列来控制子数据网格的列大小。

所以在最后一列的标题模板中,我添加了一个只有列标题的数据网格,没有行。在本专栏的单元格模板中,我添加了另一个数据网格,没有标题,只有数据行。

当我调整 headertemplate 数据网格列标题的大小时,XAML 中有什么方法可以调整单元格模板的数据网格列的大小。

阅读了很多关于 SO 的回答以及关于 CodeProject 等的一些帖子,但我无法让它发挥作用。有可能吗?

XAML

 <Grid>
            <DataGrid x:Name="Test"
                      AutoGenerateColumns="False"
                      CanUserAddRows="False"
                      ItemsSource="{Binding AllAssets}"
                      CanUserResizeColumns="True">
                <DataGrid.Resources>               
                    <DataTemplate x:Key="NewKey2">
                        <DataGrid Name="dgC"
                                  AutoGenerateColumns="False"
                                  HeadersVisibility="None"
                                  ItemsSource="{Binding months}"
                                  CanUserResizeColumns="True">
                            <DataGrid.Columns>
                                <DataGridTextColumn x:Name="Col1" Binding="{Binding value}" />
                                <DataGridTextColumn x:Name="Col2" Binding="{Binding MonthName}" />
                            </DataGrid.Columns>
                        </DataGrid>
                    </DataTemplate>
                    <DataTemplate x:Key="NewKey3">
                        <StackPanel>
                            <Label HorizontalAlignment="Center">All Headers</Label>
                            <DataGrid Name="dgH">
                                <DataGrid.Columns>
                                    <DataGridTextColumn Width="{Binding ElementName=Col1, Path=ActualWidth}" Header="Value" />
                                    <DataGridTextColumn Width="{Binding ElementName=Col2, Path=ActualWidth}" Header="Month" />
                                    <!--Error or no result on these attempts
                                    <DataGridTextColumn Header="Month" Width="{Binding Source={x:Reference Col2}, Path=ActualWidth}"/>--><!--
                                    <DataGridTextColumn >
                                        <DataGridTextColumn.Header>
                                            <TextBlock Width="{Binding Source={x:Reference Col1}, Path=ActualWidth}" >Value</TextBlock>
                                        </DataGridTextColumn.Header>
                                    </DataGridTextColumn>
                                    <DataGridTextColumn  Header="Month" />-->
                                </DataGrid.Columns>
                            </DataGrid>
                        </StackPanel>
                    </DataTemplate>
                </DataGrid.Resources>
                <DataGrid.Columns>
                    <DataGridTemplateColumn Header="Id">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <Label Content="{Binding id}" />
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTemplateColumn Header="Name">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <Label Content="{Binding name}" />
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTemplateColumn CellTemplate="{StaticResource NewKey2}"
                                            HeaderTemplate="{StaticResource NewKey3}" />
                </DataGrid.Columns>

            </DataGrid>
        </Grid>

C#类数据举例:

namespace WpfApplication1
{
    public class Assets
    {
        public List<Asset> AllAssets { get; set; }

        public Assets()
        {
            AllAssets = new List<Asset>();

            for (int i = 1; i < 3; i++)
            {
                Asset asset = new Asset();

                asset.id = i;
                asset.name = "asset " + i.ToString();

                for (int x = 1; x < 3; x++)
                {
                    MonthsData months = new MonthsData();
                    months.MonthName = "Month " + x.ToString();
                    months.value = x;
                    asset.months.Add(months);
                }

                AllAssets.Add(asset);
            }
        }

    }

    public class Asset
    {
        public int id { get; set; }
        public string name { get; set; }
        public List<MonthsData> months { get; set; }

        public Asset()
        {
            months = new List<MonthsData>();
        }
    }

    public class MonthsData 
    {
        public string MonthName { get; set; }
        public int value { get; set; }
    }
}

【问题讨论】:

  • 尝试将子节点的 minwidth 属性绑定到父节点

标签: c# wpf binding


【解决方案1】:

假设您可以在 ViewModel 中定义几个属性

public class Assets : MVVM.ViewModel.ViewModelBase
{
    public List<Asset> AllAssets { get; set; }
    private double col1Width;
    private double col2Width;
    public double Col1Width
    {
        get { return col1Width; }
        set { col1Width = value; OnPropertyChanged("Col1Width"); }
    }
    public double Col2Width
    {
        get { return col2Width; }
        set { col2Width = value; OnPropertyChanged("Col2Width"); }
    }

然后您将能够通过 helper 从 XAML 读取 Load 事件中的实际宽度

    private void Grid_Loaded(object sender, RoutedEventArgs e)
    {
        var dg = FindVisualChildByName<DataGrid>(Test, "dgC");
        assets.Col1Width = dg.Columns[0].ActualWidth;
        assets.Col2Width = dg.Columns[1].ActualWidth;

    }

并从网格的 DataContext 将它们设置回您的 XAML 绑定

                            <DataGridTextColumn Header="Value" >
                                <DataGridTextColumn.HeaderStyle>
                                    <Style TargetType="DataGridColumnHeader">
                                        <Setter Property="Width" Value="{Binding ElementName=Test, Path=DataContext.Col1Width, UpdateSourceTrigger=PropertyChanged}"/>
                                    </Style>
                                </DataGridTextColumn.HeaderStyle>
                            </DataGridTextColumn>
                            <DataGridTextColumn Header="Month" >
                                <DataGridTextColumn.HeaderStyle>
                                    <Style TargetType="DataGridColumnHeader">
                                        <Setter Property="Width" Value="{Binding ElementName=Test, Path=DataContext.Col2Width, UpdateSourceTrigger=PropertyChanged}"/>
                                    </Style>
                                </DataGridTextColumn.HeaderStyle>
                            </DataGridTextColumn>

回复cmets

正如在下面的 cmets 中正确指出的那样,我们可以避免“仅与 ViewModel 中的视图相关的属性”并管理任意(但固定)数量的列,以便 dgC 和 dgH 的宽度(假设它们列数相同)被链接

    private void Grid_Loaded(object sender, RoutedEventArgs e)
    {
        var dgC = FindVisualChildByName<DataGrid>(Test, "dgC");
        var dgH = FindVisualChildByName<DataGrid>(Test, "dgH");
        for (int i = 0; i < dgC.Columns.Count; i++)
        {
            dgH.Columns[i].Width = dgC.Columns[i].ActualWidth;
        }

    }

在这种情况下,不再需要 VM 绑定

                            <DataGridTextColumn Header="Value" />
                            <DataGridTextColumn Header="Month" />

还管理列大小调整(如果不需要,请跳过此步骤)

这比较复杂,所以只有在严格要求的情况下才可以继续。

让我们更改实用程序,使其返回所有数据网格 dgC(每行有一个 dgC)

    public static IEnumerable<T> FindVisualChildByName<T>(DependencyObject parent, string name) where T : DependencyObject
    {
        List<T> list = new List<T>();
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);
            string controlName = child.GetValue(Control.NameProperty) as string;
            if (controlName == name)
            {
                list.Add(child as T);
            }
            else
            {
                IEnumerable<T> result = FindVisualChildByName<T>(child, name);
                if (result != null)
                    list.AddRange(result);
            }
        }
        return list;
    }

现在我们将为actual width change定义一个依赖属性

    private bool _columnWidthChanging;
    private void ColumnWidthPropertyChanged(object sender, EventArgs e)
    {
        // listen for when the mouse is released
        _columnWidthChanging = true;
        if (sender != null)
            Mouse.AddPreviewMouseUpHandler(this, BaseDataGrid_MouseLeftButtonUp);
    }

    void BaseDataGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (_columnWidthChanging)
        {
            _columnWidthChanging = false;
            var dgCs = FindVisualChildByName<DataGrid>(Test, "dgC");
            var dgH = FindVisualChildByName<DataGrid>(Test, "dgH").First();
            foreach (var dgC in dgCs)
            {
                for (int i = 0; i < dgC.Columns.Count; i++)
                {

                    var column = dgH.Columns[i];
                    dgC.Columns[i].Width = column.ActualWidth;
                }
            }
        }
    }

    private void Grid_Loaded(object sender, RoutedEventArgs e)
    {
        var dgC = FindVisualChildByName<DataGrid>(Test, "dgC").First();
        var dgH = FindVisualChildByName<DataGrid>(Test, "dgH").First();
        for (int i = 0; i < dgC.Columns.Count; i++)
        {
            var column = dgC.Columns[i];
            dgH.Columns[i].Width = column.ActualWidth;

            PropertyDescriptor pd = DependencyPropertyDescriptor
                         .FromProperty(DataGridColumn.ActualWidthProperty,
                                       typeof(DataGridColumn));

                //Add a listener for this column's width
                pd.AddValueChanged(dgH.Columns[i],
                                   new EventHandler(ColumnWidthPropertyChanged));


        }

【讨论】:

  • 我对这种方法有点挣扎,因为:1.您将仅与 View 相关的属性放入 ViewModel 2.它不是很通用,不可能使用动态的列列表/特性。因此,扩展/维护 ViewModel 非常困难。虽然我不得不承认我的解决方案也没有解决这个问题;-)
  • 嗨@Mat 1) 是的,我知道。如果您愿意,我可以更改代码,使其全部在 xaml 中,并且不需要视图模型。 Xaml 的依赖属性可以替换 VM 中的对应属性。只是因为我想展示绑定如何以“常规”和简化的方式工作,避免自定义网格控件.. 2)我不认为数据网格非常适合通用数量的列:它本机只管理一般数量的行。如果没有严格要求,我会再次避免使用更复杂的解决方案。
  • 我有一个特殊的一次性场景,我事先知道列号。它们不是任意的,至少对我来说不是。所以没关系。不知道原来的OP。我已经提高了答案。 :) 我可以接受这个解决方案。
  • 太棒了....不急。来自纯 VBA 背景的这些东西对我来说是新的。感谢所有的帮助。
  • @cyboashu 没什么,不客气。现在,如果您查看我的编辑,所有这些都在后面的代码中进行管理(不需要 ViewModel),并且 dgC 和 dgH 的宽度之间存在链接(假设它们具有相同的通用 N 列)
【解决方案2】:

您可以使用Grid 并设置ColumnDefinitionSharedSizeGroup 属性。这将“自动”调整Grid.IsSharedSizeScope中定义的列的大小

如果您想了解有关此内置功能的更多信息,可以 google 或查看: https://wpf.2000things.com/tag/sharedsizegroup/

我采用了您的代码以使其正常工作。我用Grid 替换了标题/单元格模板中的DataGrid,以便使用

XAML

<Grid Grid.IsSharedSizeScope="True">
<DataGrid AutoGenerateColumns="False"
            CanUserAddRows="False"
            ItemsSource="{Binding AllAssets}"
            CanUserResizeColumns="True">
    <DataGrid.Resources>
        <DataTemplate x:Key="NewKey2">
            <ItemsControl ItemsSource="{Binding months}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Grid ShowGridLines="True">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition SharedSizeGroup="A" />
                                <ColumnDefinition SharedSizeGroup="B" />
                            </Grid.ColumnDefinitions>
                            <TextBlock Text="{Binding value}"
                                        Margin="5" />
                            <TextBlock Text="{Binding MonthName}"
                                        Margin="5"
                                        Grid.Column="2" />
                        </Grid>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </DataTemplate>
        <DataTemplate x:Key="NewKey3">
            <StackPanel>
                <Label HorizontalAlignment="Center">All Headers</Label>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="A" />
                        <ColumnDefinition SharedSizeGroup="B" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="Value"
                                Margin="5" />
                    <TextBlock Text="Month"
                                Margin="5"
                                Grid.Column="1" />
                </Grid>
            </StackPanel>
        </DataTemplate>
    </DataGrid.Resources>
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Id">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Label Content="{Binding id}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="Name"
                                Width="Auto">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Label Content="{Binding name}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn CellTemplate="{StaticResource NewKey2}"
                                HeaderTemplate="{StaticResource NewKey3}" />
    </DataGrid.Columns>
</DataGrid>

【讨论】:

  • 第一件事,我也提高了你的回答。不必遍历视觉树是好的。 :) 但是使用网格,我失去了在运行时使用鼠标调整标题/列大小的能力。有没有办法添加一个功能,允许用户在运行时调整列值和月份的大小。如果是的话,太好了。如果没有,不用担心。 :) 顺便说一下,我尝试了 GridSplitter 但无法正常工作。
猜你喜欢
  • 1970-01-01
  • 2010-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-22
  • 2011-09-28
  • 2010-11-15
相关资源
最近更新 更多