数据绑定部分在 UI 中工作,其中 ItemsControl 显示扩展器集合,每个扩展器都有一个包含两列的 DataGrid。 ItemsControl 可以从代码隐藏的 ItemsSource 获取数据,但不能从 Binding 获取数据。 Expander Headers 使用 Binding 正确显示数据,DataGrid 显示正确的行数,但 DataGrid 单元格为空。
这是 XAML:
<ItemsControl Name="TheItemsControl" ItemsSource="{Binding TheItemsSource}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander>
<Expander.Header>
<Label Content="{Binding Key}" />
</Expander.Header>
<StackPanel>
<DataGrid ItemsSource="{Binding Value}"
ColumnWidth="*"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn
Header="Label"
Binding="{Binding Key.Value.Label}" />
<DataGridTextColumn
Header="Value"
Binding="{Binding Value.DisplayValue}" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
这是内置数据模型的代码隐藏。我知道,数据模型应该是一个单独的文件,但我试图让 XAML 尽可能轻松地“查找”数据。
public class ObjectProperty
{
/// <summary>
/// Content for the left column of the DataGrid
/// </summary>
public string Label = "";
/// <summary>
/// Content for the right column of the DataGrid
/// </summary>
public string DisplayValue = "";
}//end class
/// <summary>
/// UserControl to test tabbing in a complex UI.
/// Test data is a List of KeyValuePairs, where the Key is a kind of objects (Pipe, Line, ...)
/// and Value is List of ObjectProperties, each with a Label string and a DisplayValue.
/// </summary>
public partial class test_itemcontrol_expander_datagrid : UserControl
{
/// <summary>
/// Return the list of items for the datagrid
/// </summary>
/// <param name="ObjectId"></param>
/// <returns></returns>
public List<ObjectProperty> GetRows(string ObjectId)
{// Set up the collection properties used to bind the ItemsSource
// properties to display the list of items in the dropdown lists.
string now = DateTime.Now.ToString("HH:mm:ss.ffffff");
var L = new List<ObjectProperty>();
switch (ObjectId.ToLower())
{
case "pipe":
L.Add(new ObjectProperty() { Label = "Length", DisplayValue = "26.3 m" });
L.Add(new ObjectProperty() { Label = "Material", DisplayValue = "PVC" });
L.Add(new ObjectProperty() { Label = "Substance", DisplayValue = "Tar" });
L.Add(new ObjectProperty() { Label = "Address", DisplayValue = "Mainstreet" });
L.Add(new ObjectProperty() { Label = "ObjectId", DisplayValue = ObjectId });
L.Add(new ObjectProperty() { Label = "Length", DisplayValue = ObjectId.Length.ToString() });
L.Add(new ObjectProperty() { Label = "Time", DisplayValue = now });
L.Add(new ObjectProperty() { Label = "Width", DisplayValue = "0.15 m" });
L.Add(new ObjectProperty() { Label = "Weight", DisplayValue = "16.2 kg" });
L.Add(new ObjectProperty() { Label = "Orientation", DisplayValue = "Northwest" });
L.Add(new ObjectProperty() { Label = "Flexible", DisplayValue = "Yes" });
L.Add(new ObjectProperty() { Label = "Status", DisplayValue = "Half" });
break;
case "line":
default:
L.Add(new ObjectProperty() { Label = "Length", DisplayValue = "1.23 m" });
L.Add(new ObjectProperty() { Label = "Material", DisplayValue = "PVC" });
L.Add(new ObjectProperty() { Label = "Substance", DisplayValue = "Tar" });
L.Add(new ObjectProperty() { Label = "Address", DisplayValue = "Backstreet" });
L.Add(new ObjectProperty() { Label = "ObjectId", DisplayValue = ObjectId });
L.Add(new ObjectProperty() { Label = "Length", DisplayValue = ObjectId.Length.ToString() });
L.Add(new ObjectProperty() { Label = "Time", DisplayValue = now });
L.Add(new ObjectProperty() { Label = "Width", DisplayValue = "0.15 m" });
L.Add(new ObjectProperty() { Label = "Weight", DisplayValue = "16.2 kg" });
L.Add(new ObjectProperty() { Label = "Orientation", DisplayValue = "Northwest" });
L.Add(new ObjectProperty() { Label = "Flexible", DisplayValue = "Yes" });
L.Add(new ObjectProperty() { Label = "Status", DisplayValue = "Half" });
break;
};
return L;
}
/// <summary>
/// Return the list of items for the datagrid as a Key Values Pair
/// </summary>
/// <param name="ObjectId"></param>
/// <returns></returns>
public KeyValuePair<string, List<ObjectProperty>> GetRows_KVP(string ObjectId)
{
var items = GetRows(ObjectId);
var itemsKvp = new KeyValuePair<string, List<ObjectProperty>>(ObjectId, items);
return itemsKvp;
}
/// <summary>
/// The entire dataset for display in the UserControl
/// </summary>
/// <returns></returns>
public List<KeyValuePair<string, List<ObjectProperty>>> TheItemsSource()
{
var itemsSource = new List<KeyValuePair<string, List<ObjectProperty>>>();
itemsSource.Add(GetRows_KVP("Pipe"));
itemsSource.Add(GetRows_KVP("Line"));
return itemsSource;
}
/// <summary>
/// Class initializer
/// </summary>
public test_itemcontrol_expander_datagrid()
{
InitializeComponent();
//TheItemsControl.DataContext = this;
TheItemsControl.ItemsSource = TheItemsSource();
}
}//end class
如果我从 XAML 中的 ItemsControl 中删除 ItemsSource 属性,Expander 标头仍会获取数据。
如果我更改代码隐藏初始化程序以启用 DataContext 分配并注释掉 ItemsSource 分配,则 Expander 标头不会获取任何数据。
我对 DataGrid 列中的绑定的最佳猜测是:
Binding="{Binding Path=Label}"
但我尝试了几种变体,例如:
Binding="{Binding Label}"
Binding="{Binding Key.Value.Label}"
Binding="{Binding Value.DisplayValue}"
我还尝试了 Live Visual Tree,它可以很好地显示各种控件的嵌套,但我想知道是否以及如何看到流经 Binding 的数据。我确实看到两个小点表明此绑定是错误的,但 DataGrid 列中的绑定上没有这样的指示:
所以我想知道一个凡人如何调试这样的绑定问题?
原始问题的解决方案是将对
TheItemsSource
方法的绑定替换为对属性的绑定(在您的情况下,该属性应该是依赖属性)。
也不要从控件内部设置 DataContext。这将破坏您或控件的用户定义的外部数据绑定。使用
Binding.ElementName
或 Binding.RelativeSource
将内部元素绑定到父控件的属性。
请注意:您的类有一个非常奇怪且不常见的命名约定。通用约定使用 PascalCase 命名类型。就像所有 WPF 和 .NET 类一样。它不是“items_control”,而是“ItemsControl”。
您还应该避免将
Label.Content
属性绑定到 string
。一般来说,不要使用 Label
显示文本。使用 TextBlock
,它针对显示小文本值进行了高度优化。
还可以考虑将
ItemsControl
替换为 ListBox
,它的性能也明显更好(它是扩展的 ItemsControl
)。
test_itemcontrol_expander_datagrid.xaml
<UserControl x:Name="Root">
<ItemsControl ItemsSource="{Binding ElementName=Root, Path=TheItems}" />
</UserControl>
test_itemcontrol_expander_datagrid.xaml.cs
partial class test_itemcontrol_expander_datagrid : UserControl
{
public IList TheItems
{
get => (IList)GetValue(TheItemsProperty);
set => SetValue(TheItemsProperty, value);
}
public static readonly DependencyProperty TheItemsProperty = DependencyProperty.Register(
"TheItems",
typeof(IList),
typeof(test_itemcontrol_expander_datagrid),
new PropertyMetadata(default));
public test_itemcontrol_expander_datagrid()
{
InitializeComponent();
this.TheItems = new ObservableCollection<object>();
// Initialize the control from the Loaded event
// to avoid time consuming work in the constructor.
// Important: you can use async/await outside the constructor!
this.Loaded += OnLoaded;
}
// Declare as async void if you need to use asynchronous initiaization
private void OnLoaded(object sender, RoutedEventArgs e)
=> TheItemsSource();
private void TheItemsSource()
{
// Reuse the existing collection instance to improve
// rendering performance and memory management
this.TheItems.Clear();
this.TheItems.Add(GetRows_KVP("Pipe"));
this.TheItems.Add(GetRows_KVP("Line"));
}
}