具有级联数据上下文到集合中的子元素的自定义控件

7

我正在尝试创建自定义用户控件,与DataGrid具有类似的功能(但DataGrid在这里不是正确的选项)。

我想要实现的是像这样的功能:

<my:CustomList ItemsSource="{Binding Items}">
    <my:CustomList.Columns>
        <my:Column Width="60" Binding="{Binding MyCustomProperty}" />
    </my:CustomList.Columns>
</my:CustomList>

Items将从ViewModel中获取(例如):

public ObservableCollection<Item> Items { get; set; }

public class Item
{
    public string MyCustomProperty { get; set; }
    public string MyAnotherCustomProperty { get; set; }
}

我的问题在于绑定到MyCustomProperty。

如果我从DataGrid继承自定义控件并使用其列,则DataContext可以很好地从ItemsSource流动到列中的Bindings。我希望对于不继承自DataGrid的自定义控件也是如此。DataGrid.Columns从ItemsSource获取上下文的神奇原理是什么?

编辑: 让我用另一种方式问这个问题:

如果我实现自定义DataGridColumn

public class MyDataGridColumn : DataGridBoundColumn
{
    private Binding _bindingSubText;

    public Binding BindingSubText
    {
        get
        {
            return _bindingSubText;
        }
        set
        {
            if (_bindingSubText == value) return;
            var oldBinding = _bindingSubText;
            _bindingSubText = value;
            OnBindingChanged(oldBinding, _bindingSubText);
        }
    }

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        var textTextBlock = new TextBlock();
        var bindingText = Binding ?? new Binding();
        textTextBlock.SetBinding(TextBlock.TextProperty, bindingText);

        var textSubTextBlock = new TextBlock();
        var bindingSubText = BindingSubText ?? new Binding();
        textSubTextBlock.SetBinding(TextBlock.TextProperty, bindingSubText);

        var stackPanel = new StackPanel() { Orientation = Orientation.Vertical };
        stackPanel.Children.Add(textTextBlock);
        stackPanel.Children.Add(textSubTextBlock);

        return stackPanel;
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        // I don't want to edit elements
        return null;
    }
}

并尝试在XAML中像这样使用它:

<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <my:MyDataGridColumn Binding="{Binding MyCustomProperty}" BindingSubText="{Binding MyAnotherCustomProperty}" />
    </DataGrid.Columns>
</DataGrid>

绑定 BindingSubText 属性仍将使用 DataGrid 父级的 DataContext,为我提供 Items。MyAnotherCustomProperty 在设计时会有波浪线,但运行时会正常工作(因为动态绑定)。我的问题是当其他人使用此自定义 DataGridColumn 时,他/她需要知道这一点,并且绑定的 IntelliSense 会出现“错误”。

如何设置 DataGridColumn 的 Binding 属性的上下文,以便 IntelliSense 能够按预期工作?


1
你从哪个控件派生而来?它源自于ItemsControl并管理它们。 - ZSH
我的也来自于ItemsControl。 - Janez Lukan
你是如何定义my:CustomList.Columns(制作测试程序)的? - ZSH
公共ObservableCollection<Column>列 {get; set;}其中Column具有依赖属性GridLength宽度和BindingBase绑定。 - Janez Lukan
这似乎有点复杂,我发现自己在阅读DataGrid源代码,请参见http://www.dotnetframework.org/default.aspx/4@0/4@0/DEVDIV_TFS/Dev10/Releases/RTMRel/wpf/src/Framework/System/Windows/Controls/DataGrid@cs/1407647/DataGrid@cs。 - ZSH
我认为这其中有很多东西似乎不是表面上看起来那么简单。我在过去的一个小时里查看了源代码,但无法准确指出问题所在,但我想它可能与ItemTemplate有关。你Columns集合中的每个对象都应该应用DataGrid的ItemTemplate,但我不确定如何做到这一点。请查看源代码 - Kcvin
2个回答

2
你的问题非常广泛,但首先如果你想扩展DataGrid,请考虑覆盖其样式并添加触发器或类似的东西。
这关乎你的设计...很难说哪种是正确的或错误的。
DataGrid和GridView中的columns属性或多或少只是一个虚拟对象,保存单元格稍后需要的值,例如单元格模板、绑定、宽度、高度等等。可以这样说,columns没有真正的DataContext...
DataContext会在功能性视觉/逻辑树中传递,但columns属性并不在其中。这就是你遇到问题的原因。你可以在这样的列对象中设置的所有绑定都是占位符。然而,单元格参与了可视化树,因为它们是自己的控件,所以一旦生成单元格,它们会执行这样的操作,然后再被绘制:cellTextBlock.SetBinding(TextBlock.TextProperty,columnBindingPlaceHolder)、cellTextBlock.SetBinding(TextBlock.HeightProperty,columnHeightPlaceHolder)。
单元格使用来自列的这些占位符。
如果你希望自定义列属性具有与DataGrid相同的DataContext,请考虑将ObseravableCollection更改为FreezableCollection。也可以将Column对象作为Freezable对象。只需使用Freezables。因为在WPF中,它们具有继承DataContext的能力。例如,在Wpf中Brushes是freezables。
希望这能帮助你进一步。

1
然而,从逻辑上讲,人们会期望在Visual/Logical Tree元素上的属性能够从工具中获取DataContext的帮助。也许问题出在工具上...根据你所说的,这样暗示着是这个问题,还是我错了? - Anže Vodovnik
我尝试使用Freezables,但离解决方案还很远。无论如何,感谢您的帮助。我想你赢得了悬赏 :) - Janez Lukan
抱歉啊,我刚才在忙着吃东西,顺便祝你圣诞快乐 :) 这是一篇关于可冻结对象(Freezable)的好文章 http://joshsmithonwpf.wordpress.com/2008/07/22/enable-elementname-bindings-with-elementspy/Josh Smith 在 WPF 方面很有经验,他创建了一个从可冻结对象(Freezable)继承而来的 ElementSpy 类。 - dev hedgehog

-1

我认为,DataGrid的祖先之一处理了这个问题(也许是ItemsControl)。如果您的控件不是从ItemsControl派生的,则必须手动处理此问题(在向控件添加新列时,显式设置其数据上下文)。

现在我看到您的控件也是从ItemsControl派生的。那么我建议手动处理它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接