【问题标题】:Data binding doesn't work on Custom controls inside collection数据绑定不适用于集合内的自定义控件
【发布时间】:2019-09-21 07:59:48
【问题描述】:

WPF 数据绑定不适用于在 xaml 集合标记内定义的自定义控件。我只想在自定义控件中定义一组自定义小部件,并将一些小部件属性与视图模型属性绑定。像这样。

<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <local:MyCustomControl>
            <local:MyCustomControl.Widgets>
                <local:MyCustomWidget ImportantToggle="{Binding SomeToggle}"/>
            </local:MyCustomControl.Widgets>
        </local:MyCustomControl>
    </Grid>
</Window>

这是我的自定义控件。我对小部件使用 obseravblecollection 并在构造函数中调用 SetValue 以稍后获取 propertychanged 回调(现在未在示例中使用)

using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;

namespace WpfApp1
{
    public class MyCustomControl : FrameworkElement
    {
        public ObservableCollection<MyCustomWidget> Widgets
        {
            get { return (ObservableCollection<MyCustomWidget>)this.GetValue(WidgetsProperty); }
            set { this.SetValue(WidgetsProperty, value); }
        }
        public static DependencyProperty WidgetsProperty = DependencyProperty.Register("Widgets", typeof(ObservableCollection<MyCustomWidget>), typeof(MyCustomControl), new PropertyMetadata(null, (e, args) => ((MyCustomControl)e).WidgetsChanged(args)));

        public void WidgetsChanged(DependencyPropertyChangedEventArgs e)
        {
            Debug.WriteLine("widgets collection object changed inside my custom control!");
        }

        public MyCustomControl()
        {
            this.SetValue(WidgetsProperty, new ObservableCollection<MyCustomWidget>());
        }
    }
}

那是我的自定义小部件:

namespace WpfApp1
{
    public class MyCustomWidget : FrameworkContentElement
    {
        public bool ImportantToggle
        {
            get { return (bool)this.GetValue(ImportantToggleProperty); }
            set { this.SetValue(ImportantToggleProperty, value); }
        }

        public static DependencyProperty ImportantToggleProperty = DependencyProperty.Register("ImportantToggle", typeof(bool), typeof(MyCustomWidget), new PropertyMetadata(false, (e, args) => ((MyCustomWidget)e).ImportantToggleChanged(args)));

        public void ImportantToggleChanged(DependencyPropertyChangedEventArgs e)
        {
            Debug.WriteLine("my toggle changed inside my custom widget!");
        }
    }
}

最后是我的简单视图模型:

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace WpfApp1
{
    public class MainViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private bool _someToggle;
        public bool SomeToggle
        {
            get { return this._someToggle; }
            set
            {
                this._someToggle = value;
                this.NotifyPropertyChanged();
            }
        }

        public MainViewModel()
        {
            this.SomeToggle = !this.SomeToggle;
        }
    }
}

这是我从 Debug.Writeline 得到的输出:在我的自定义控件中更改了小部件集合对象!

观察:我无法绑定 MyCustomWidget 的属性。我知道在这种情况下绑定可能会失败,因为 observablecollection 是在 mycustomcontrol 的构造函数内部创建的,但我不知道如何修复它以使绑定在 mycustomwidget 内部工作。

【问题讨论】:

    标签: wpf xaml data-binding observablecollection dependency-properties


    【解决方案1】:

    要使该绑定起作用,您的 local:MyCustomWidget 需要具有与主窗口相同的 DataContext。 WPF 元素继承其逻辑父级的 DataContext。 MyCustomWidget 没有,因为它不在逻辑树中。它只是坐在那里。您不会将其添加到其父级的任何类型的普通子集合中,只是添加到框架不知道的随机 ObservableCollection 中。

    下面的代码可能是粗略的破解。我还没有调查过WPF的这个角落。我以最大的诚意敦促您找出正确的方法。但是,在您的代码中添加此内容后,我在初始化绑定时点击了 MyCustomWidget 中的 propertychanged 事件。

    public MyCustomControl()
    {
        this.SetValue(WidgetsProperty, new ObservableCollection<MyCustomWidget>());
        Widgets.CollectionChanged += Widgets_CollectionChanged;
    }
    
    private void Widgets_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems is System.Collections.IEnumerable)
        {
            foreach (MyCustomWidget widget in e.NewItems)
            {
                AddLogicalChild(widget);
            }
        }
    }
    

    顺便说一句,您可以省去在 MainViewModel 构造函数中切换开关的麻烦。这发生在绑定存在之前很久。我添加了一个复选框:

    <StackPanel>
        <CheckBox IsChecked="{Binding SomeToggle}">Test Toggle</CheckBox>
        <local:MyCustomControl>
            <local:MyCustomControl.Widgets>
                <local:MyCustomWidget 
                    ImportantToggle="{Binding SomeToggle}"
                    />
            </local:MyCustomControl.Widgets>
        </local:MyCustomControl>
    </StackPanel>
    

    更新:

    这完全省略了您的 Widgets 集合,并且绑定工作无需我们付出任何努力。子小部件将位于 MyCustomControl.Children 中。重要的是,我们不再将子类型限制为MyCustomWidget。这是一项重大的设计更改,可能不符合您的要求。您可以仔细检查Panel 类,并编写一个以相同方式工作的类,但只接受一种类型的孩子(这意味着编写UIElementCollection 的模拟,这将是一大堆乏味的样板) .

    MyCustomControl.cs

    [ContentProperty("Children")]
    public class MyCustomControl : Panel
    {
    }
    

    MyCustomWidget.cs

    public class MyCustomWidget : Control
    {
        public bool ImportantToggle
        {
            get { return (bool)this.GetValue(ImportantToggleProperty); }
            set { this.SetValue(ImportantToggleProperty, value); }
        }
    
        public static DependencyProperty ImportantToggleProperty = 
            DependencyProperty.Register("ImportantToggle", typeof(bool), typeof(MyCustomWidget), 
                new PropertyMetadata(false, (e, args) => ((MyCustomWidget)e).ImportantToggleChanged(args)));
    
        public void ImportantToggleChanged(DependencyPropertyChangedEventArgs e)
        {
            Debug.WriteLine("my toggle changed inside my custom widget!");
        }
    }
    

    MainWindow.xaml

    <local:MyCustomControl>
        <local:MyCustomWidget 
            ImportantToggle="{Binding SomeToggle}"
            />
    </local:MyCustomControl>
    

    【讨论】:

    • 效果很好。我不知道 AddLogicalChild。还有一个问题:我执行 e.NewValue 和 e.OldValue 订阅和取消订阅...我是否还必须删除 OldValue 上的逻辑子项?您对切换的看法也是正确的。我只用它在 get 上设置断点来测试绑定:)
    • @D3d_dev 是的,我会删除 e.OldValue 中的任何内容,只是万一某天可能会删除它们。但请注意,这些集合可以为空,如果您遍历空集合,foreach 将抛出异常。然而,我真正要做的是考虑使用 WPF 自己的子机制而不是我自己的。
    • 您能否指出使用 wpf 子机制的正确方向?我打算写下一些不属于 wpf 布局的特殊小部件,但只是将额外的数据提供给我的特殊控件。实际上,我的特殊控件是自定义 3d 查看器,小部件是可以通过数据绑定控制的 3d gizmo。我不知道如何在这种情况下使用标准的 wpf 机制。干杯。
    猜你喜欢
    • 1970-01-01
    • 2012-10-11
    • 2014-02-28
    • 2013-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-03
    相关资源
    最近更新 更多