如何调试和修复嵌套 wpf 控件中的绑定

问题描述 投票:0回答:1

数据绑定部分在 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 列中的绑定上没有这样的指示:

所以我想知道一个凡人如何调试这样的绑定问题?

c# wpf xaml binding
1个回答
0
投票

原始问题的解决方案是将对

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"));
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.