在 XAML 中为用户控件创建复合 DataContext

发布于 2024-11-25 13:19:39 字数 1525 浏览 1 评论 0原文

我正在尝试为 UserControl 创建复合 DataContext 。基本上,我有一个具有 OrderPackage 属性的控件,我想在 XAML 而不是代码中创建表示此数据源的复合对象。

这就是我尝试显示 UserControl(并创建 DataContext)的方式:

<views:PackageDetailsControl>
    <views:PackageDetailsControl.DataContext>
        <vm:OrderPackagePair Package="{Binding Package, Mode=OneWay}" 
                             Order="{Binding Order, Mode=OneWay}"/>                 
    </views:PackageDetailsControl.DataContext>
</views:PackageDetailsControl>  

OrderPackagePair 对象是在 XAML 中创建的简单依赖项对象:

public class OrderPackagePair : DependencyObject
{
    public OrderDetails Order
    {
        get { return (OrderDetails)GetValue(OrderProperty); }
        set { SetValue(OrderProperty, value); }
    }

    public static readonly DependencyProperty OrderProperty =
        DependencyProperty.Register("Order", typeof(OrderDetails), typeof(OrderPackagePair), new UIPropertyMetadata(null));

    public PackageInfo Package
    {
        get { return (PackageInfo)GetValue(PackageProperty); }
        set { SetValue(PackageProperty, value); }
    }

    public static readonly DependencyProperty PackageProperty =
        DependencyProperty.Register("Package", typeof(PackageInfo), typeof(OrderPackagePair), new UIPropertyMetadata(null));
}

OrderPackage 未正确绑定,并且为空。

是的,我知道可能有更好的方法来做到这一点 - 但我不明白为什么这不起作用。有时在 Blend 中它会起作用,然后又变成空白。

I am trying to create a composite DataContext for a UserControl. Basically I have a control which has Order and Package properties and I wanted to create the composite object representing this datasource in XAML rather than in code.

This is how I am trying to display the UserControl (and create the DataContext):

<views:PackageDetailsControl>
    <views:PackageDetailsControl.DataContext>
        <vm:OrderPackagePair Package="{Binding Package, Mode=OneWay}" 
                             Order="{Binding Order, Mode=OneWay}"/>                 
    </views:PackageDetailsControl.DataContext>
</views:PackageDetailsControl>  

The OrderPackagePair object is a simple dependency object that is created in XAML :

public class OrderPackagePair : DependencyObject
{
    public OrderDetails Order
    {
        get { return (OrderDetails)GetValue(OrderProperty); }
        set { SetValue(OrderProperty, value); }
    }

    public static readonly DependencyProperty OrderProperty =
        DependencyProperty.Register("Order", typeof(OrderDetails), typeof(OrderPackagePair), new UIPropertyMetadata(null));

    public PackageInfo Package
    {
        get { return (PackageInfo)GetValue(PackageProperty); }
        set { SetValue(PackageProperty, value); }
    }

    public static readonly DependencyProperty PackageProperty =
        DependencyProperty.Register("Package", typeof(PackageInfo), typeof(OrderPackagePair), new UIPropertyMetadata(null));
}

Order and Package are not bound correctly and are just null.

Yes I know there's probably a better way of doing this - but I cannot understand why this isn't working. Occasionally in Blend it'll work and then go blank again.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

转角预定愛 2024-12-02 13:19:39

这将不起作用,因为 DependencyObjectOrderPackagePair 类)不会监视其依赖项属性的内部更改。由于 OrderPackagePair 对象保持不变,因此 DataContext 视为未更改。

在相反的站点上,Freezable 类旨在通知订阅者,当其依赖属性之一发生更改时,实例已更改。

因此,尝试将 Freezable 而不是 DependencyObject 声明为 OrderPackagePair 的基类。

------------- 更新 --------

是的,它有效。为了证明这一点,我实现了一个简单的例子。

OrderPackagePairClass 的代码:

public class OrderPackagePair : Freezable
{
    public OrderDetails Order
    {
        get { return (OrderDetails)GetValue(OrderProperty); }
        set { SetValue(OrderProperty, value); }
    }

    public static readonly DependencyProperty OrderProperty =
        DependencyProperty.Register("Order", typeof(OrderDetails), typeof(OrderPackagePair), new UIPropertyMetadata(null));

    public PackageInfo Package
    {
        get { return (PackageInfo)GetValue(PackageProperty); }
        set { SetValue(PackageProperty, value); }
    }

    public static readonly DependencyProperty PackageProperty =
        DependencyProperty.Register("Package", typeof(PackageInfo), typeof(OrderPackagePair), new UIPropertyMetadata(null));

    protected override Freezable CreateInstanceCore()
    {
        throw new NotImplementedException();
    }
}

XAML:

<Window x:Class="WindowTest.MainWindow"
        xmlns:self="clr-namespace:WindowTest"
        Name="RootControl">
    <StackPanel Margin="10" DataContextChanged="StackPanel_DataContextChanged">
        <StackPanel.DataContext>
            <self:OrderPackagePair Package="{Binding Path=DataContext.PackageInfo, Mode=OneWay, ElementName=RootControl}" 
                                   Order="{Binding Path=DataContext.OrderDetails, Mode=OneWay, ElementName=RootControl}"/>
        </StackPanel.DataContext>

        <Button Margin="10" Content="Change Package" Click="Button_Click"/>
    </StackPanel>
</Window> 

以及背后的代码:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    private OrderDetails _orderDetails;
    public OrderDetails OrderDetails
    {
        get
        {
            return this._orderDetails;
        }
        set
        {
            this._orderDetails = value;
            this.OnPropertyChanged("OrderDetails");
        }
    }

    private PackageInfo _packageInfo;
    public PackageInfo PackageInfo
    {
        get
        {
            return this._packageInfo;
        }
        set
        {
            this._packageInfo = value;
            this.OnPropertyChanged("PackageInfo");
        }
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.PackageInfo = new PackageInfo(DateTime.Now.ToString());
    }

    private void StackPanel_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        Trace.WriteLine("StackPanel.DataContext changed");
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string name)
    {
        var safeEvent = this.PropertyChanged;
        if (safeEvent != null)
        {
            safeEvent(this, new PropertyChangedEventArgs(name));
        }
    }
}

单击按钮时,模型会更改 PackageInfo 属性(为简单起见,模型和视图在同一个类中实现)。依赖属性 OrderPackagePair.Package 对新值做出反应并覆盖其值。由于 Freezable 性质,OrderPackagePair 会通知所有订阅者它已更改,并调用处理程序 StackPanel_DataContextChanged。如果您返回 DependencyObject 作为 OrderPackagePair 的基类 - 处理程序将永远不会被调用。

所以,我想你的代码由于其他错误而无法工作。您应该仔细使用 DataContext。例如,您写道:

<views:PackageDetailsControl>
    <views:PackageDetailsControl.DataContext>
        <vm:OrderPackagePair Package="{Binding Package, Mode=OneWay}" 
                             Order="{Binding Order, Mode=OneWay}"/>                 
    </views:PackageDetailsControl.DataContext>
</views:PackageDetailsControl>

这当然是问题之一。绑定表达式面向当前的DataContext。但是您将 DataContext 设置为 OrderPackagePair 实例。因此,您将 OrderPackagePair.Package 绑定到 OrderPackagePair.Package (我想,您的目标是将 OrderPackagePair.Package 绑定到 Model.Package)。这就是为什么什么也没发生。

在我的绑定表达式示例中,我明确告知我要绑定到哪个 DataContext:

 Package="{Binding Path=DataContext.PackageInfo, Mode=OneWay, ElementName=RootControl}"

This will not work because DependencyObject(OrderPackagePair class) doesn't monitor internal changes of its dependency properties. As OrderPackagePair object remains the same, DataContext considered as unchanged.

On the opposite site, class Freezable is intented to notify subscribers that instance was changed when one of its dependency properties changed.

So, try to declare Freezable instead of DependencyObject as base class of OrderPackagePair.

------------- UPDATE --------

Yes, it works. In order to prove it I've implemented simple example.

Code of OrderPackagePairClass:

public class OrderPackagePair : Freezable
{
    public OrderDetails Order
    {
        get { return (OrderDetails)GetValue(OrderProperty); }
        set { SetValue(OrderProperty, value); }
    }

    public static readonly DependencyProperty OrderProperty =
        DependencyProperty.Register("Order", typeof(OrderDetails), typeof(OrderPackagePair), new UIPropertyMetadata(null));

    public PackageInfo Package
    {
        get { return (PackageInfo)GetValue(PackageProperty); }
        set { SetValue(PackageProperty, value); }
    }

    public static readonly DependencyProperty PackageProperty =
        DependencyProperty.Register("Package", typeof(PackageInfo), typeof(OrderPackagePair), new UIPropertyMetadata(null));

    protected override Freezable CreateInstanceCore()
    {
        throw new NotImplementedException();
    }
}

XAML:

<Window x:Class="WindowTest.MainWindow"
        xmlns:self="clr-namespace:WindowTest"
        Name="RootControl">
    <StackPanel Margin="10" DataContextChanged="StackPanel_DataContextChanged">
        <StackPanel.DataContext>
            <self:OrderPackagePair Package="{Binding Path=DataContext.PackageInfo, Mode=OneWay, ElementName=RootControl}" 
                                   Order="{Binding Path=DataContext.OrderDetails, Mode=OneWay, ElementName=RootControl}"/>
        </StackPanel.DataContext>

        <Button Margin="10" Content="Change Package" Click="Button_Click"/>
    </StackPanel>
</Window> 

And code behind:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    private OrderDetails _orderDetails;
    public OrderDetails OrderDetails
    {
        get
        {
            return this._orderDetails;
        }
        set
        {
            this._orderDetails = value;
            this.OnPropertyChanged("OrderDetails");
        }
    }

    private PackageInfo _packageInfo;
    public PackageInfo PackageInfo
    {
        get
        {
            return this._packageInfo;
        }
        set
        {
            this._packageInfo = value;
            this.OnPropertyChanged("PackageInfo");
        }
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.PackageInfo = new PackageInfo(DateTime.Now.ToString());
    }

    private void StackPanel_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        Trace.WriteLine("StackPanel.DataContext changed");
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string name)
    {
        var safeEvent = this.PropertyChanged;
        if (safeEvent != null)
        {
            safeEvent(this, new PropertyChangedEventArgs(name));
        }
    }
}

When you click the button, model changes PackageInfo property (for simplicity model and view are implemented in the same class). Dependency property OrderPackagePair.Package reacts on new value and overwrites its value. Due to Freezable nature, OrderPackagePair notifies all subscribers that it was changed and handler StackPanel_DataContextChanged is called. If you get back to DependencyObject as base class of OrderPackagePair - handler will be never called.

So, I suppose your code doesn't work because of other mistakes. You should carefully work with DataContext. For example, you wrote:

<views:PackageDetailsControl>
    <views:PackageDetailsControl.DataContext>
        <vm:OrderPackagePair Package="{Binding Package, Mode=OneWay}" 
                             Order="{Binding Order, Mode=OneWay}"/>                 
    </views:PackageDetailsControl.DataContext>
</views:PackageDetailsControl>

and certainly this is one of the problems. Binding expression is oriented on current DataContext. But you set DataContext as OrderPackagePair instance. So you binded OrderPackagePair.Package to OrderPackagePair.Package (I suppose, that your goal is to bind OrderPackagePair.Package to Model.Package). And that's why nothing happened.

In my example in binding expression I explicitly tell to which DataContext I want to bind:

 Package="{Binding Path=DataContext.PackageInfo, Mode=OneWay, ElementName=RootControl}"
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文