【发布时间】:2021-07-04 05:32:42
【问题描述】:
首先,让我为这篇文章的篇幅道歉 - 我知道它很长,但我认为对于这篇文章,更多的细节总比更少的好。
我现在想要实现的是数据网格的总计页脚行。由于它需要显示在底行,我采用的方法是添加一些与数据网格中的列对齐的 TextBlock。
我的应用程序在 ItemsControl 中有多个数据网格,因此我还没有找到一种设置绑定的好方法。使用 RelativeSource 似乎不是一个选项,因为没有办法(据我所知)将其指向后代元素,然后搜索特定的子元素。所以我写了一些hackery来在后面的代码中做我想做的事情。
好的,现在问题来了。当应用程序启动时一切看起来都很好,但是一旦网格中的任何项目发生变化,宽度绑定似乎就完全中断了。我写了一个小测试应用程序来说明我的意思。下面是一些截图:
现在,如果我单击按钮更改元素,页脚文本块的宽度绑定会中断:
我完全不知道这种行为的原因。我对 WPF 很陌生,只是在构建我的第一个应用程序时感到困惑。因此,如果这是一种愚蠢的做事方式,请告诉我。这是我的代码。
MainWindow.xaml:
<Window x:Class="WpfTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfTestApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Orientation="Vertical">
<ItemsControl x:Name="BarsItemsControl" ItemsSource="{Binding Bars}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Description}" />
<DataGrid x:Name="FooGrid"
ItemsSource="{Binding Foos}"
IsSynchronizedWithCurrentItem="False"
AutoGenerateColumns="False"
SelectionUnit="Cell"
SelectionMode="Extended"
CanUserReorderColumns="False"
CanUserAddRows="True"
HeadersVisibility="Column">
<DataGrid.Columns>
<DataGridTextColumn Header="Col 1" Width="*" Binding="{Binding Value1}" />
<DataGridTextColumn Header="Col 2" Width="*" Binding="{Binding Value2}" />
<DataGridTextColumn Header="Col 3" Width="*" Binding="{Binding Value3}" />
</DataGrid.Columns>
</DataGrid>
<StackPanel x:Name="TotalsRow" Orientation="Horizontal">
<TextBlock Text="{Binding Totals[0]}" />
<TextBlock Text="{Binding Totals[1]}" />
<TextBlock Text="{Binding Totals[2]}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Change something" Click="Button_Click" />
</StackPanel>
</Window>
MainWindow.xaml.cs
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace WpfTestApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public IList<Bar> Bars { get; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.Bars = new ObservableItemsCollection<Bar>();
var foos = new ObservableItemsCollection<Foo>();
for (int i = 0; i < 5; i++)
{
foos.Add(new Foo()
{
Value1 = 14.23,
Value2 = 53.23,
Value3 = 35.23
});
}
var foos2 = new ObservableItemsCollection<Foo>();
for (int i = 0; i < 5; i++)
{
foos2.Add(new Foo()
{
Value1 = 14.23,
Value2 = 53.23,
Value3 = 35.23
});
}
this.Bars.Add(new Bar(foos)
{
Description = "Bar 1",
});
this.Bars.Add(new Bar(foos2)
{
Description = "Bar 2",
});
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// Bind widths of the TotalsRow textblocks (footers) to the width of the
// datagrid column they're associated with
var elements = new List<FrameworkElement>();
this.GetChildElementsByName(this.BarsItemsControl, "FooGrid", ref elements);
foreach (var element in elements)
{
var dataGrid = element as DataGrid;
if (dataGrid != null)
{
var totalsRowList = new List<FrameworkElement>();
this.GetChildElementsByName(VisualTreeHelper.GetParent(dataGrid), "TotalsRow", ref totalsRowList);
if (totalsRowList.Count > 0)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(totalsRowList[0]); i++)
{
var textBlock = VisualTreeHelper.GetChild(totalsRowList[0], i) as TextBlock;
Binding widthBinding = new Binding();
widthBinding.Source = dataGrid.Columns[i];
widthBinding.Path = new PropertyPath("ActualWidth");
widthBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(textBlock, TextBlock.WidthProperty, widthBinding);
}
}
}
}
}
/// <summary>
/// Populate a list of elements in the visual tree with the given name under the given parent
/// </summary>
public void GetChildElementsByName(DependencyObject parent, string name, ref List<FrameworkElement> elements)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
var element = child as FrameworkElement;
if (element != null && element.Name == name)
{
elements.Add(element);
}
GetChildElementsByName(child, name, ref elements);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Bars[0].Foos[3].Value1 = 10;
}
}
}
Foo.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfTestApp
{
public class Foo : INotifyPropertyChanged
{
private double value1;
public double Value1 {
get { return value1; }
set { value1 = value; OnPropertyChanged(); }
}
private double value2;
public double Value2
{
get { return value2; }
set { value2 = value; OnPropertyChanged(); }
}
private double value3;
public double Value3
{
get { return value3; }
set { value3 = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
Bar.cs
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
namespace WpfTestApp
{
public class Bar : INotifyPropertyChanged
{
public Bar(ObservableItemsCollection<Foo> foos)
{
this.Foos = foos;
this.Totals = new double[3] { 14, 14, 14};
this.Foos.CollectionChanged += Foos_CollectionChanged;
}
private void Foos_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//var fooList = this.Categories.Cast<CategoryViewModel>();
this.Totals[0] = this.Foos.Sum(f => f.Value1);
this.Totals[1] = this.Foos.Sum(f => f.Value2);
this.Totals[2] = this.Foos.Sum(f => f.Value3);
OnPropertyChanged(nameof(Totals));
}
public string Description { get; set; }
public ObservableItemsCollection<Foo> Foos { get; }
public double[] Totals { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
ObservableItemsCollection.cs
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace WpfTestApp
{
public class ObservableItemsCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
private void Handle(object sender, PropertyChangedEventArgs args)
{
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, null));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (object t in e.NewItems)
{
((T)t).PropertyChanged += Handle;
}
}
if (e.OldItems != null)
{
foreach (object t in e.OldItems)
{
((T)t).PropertyChanged -= Handle;
}
}
base.OnCollectionChanged(e);
}
}
}
【问题讨论】:
标签: c# wpf xaml data-binding