WPF 与自定义内容控件上的 DataContext 绑定

WPF Binding with DataContext on Custom Content Control

我有一个自定义向导控件 WizardControl 派生自 UserControl,它有一个名为 Pages 的依赖项 属性,其数据类型为我的自定义 class名为 WizardPageCollection.

的集合

WizardControl 托管在具有名为 MainViewModel 的视图模型的 Window 中,向导的页面使用 XAML.

实例化

我正在尝试将页面绑定到子视图模型 Page1VMPage2VM 声明为 MainViewModel.

上的属性

DataContextPage1VM 的第一页绑定工作正常,但是第二页的绑定失败并显示以下错误消息:

System.Windows.Data Error: 3 : Cannot find element that provides DataContext. BindingExpression:Path=Page2VM; DataItem=null; target element is 'MyPage' (Name=''); target property is 'DataContext' (type 'Object')

问。为什么绑定在第一页上有效,但在第二页上失败,有没有办法让它工作,同时仍然保持 DataContext XAML 标签内声明的 MainViewModel MainWindow?我不想将 ViewModel 用作字典资源,因为这对我们有一些影响,我不会详细介绍。

根据评论者的建议,如果我将绑定更改为使用 RelativeSource,如下所示:

<common:MyPage DataContext="{Binding DataContext.Page1VM, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
<common:MyPage DataContext="{Binding DataContext.Page2VM, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />

第一个绑定工作正常,但第二个仍然失败,但出现不同的错误消息(如预期):

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=DataContext.Page2VM; DataItem=null; target element is 'MyPage' (Name=''); target property is 'DataContext' (type 'Object')

感谢您的宝贵时间!

我的代码清单如下:

主窗口XAML:

<Window.DataContext>
    <common:MainViewModel />
</Window.DataContext>
<Grid>
    <common:WizardControl>
        <common:WizardControl.Pages>
            <common:WizardPageCollection>
                <common:MyPage DataContext="{Binding Page1VM}" />
                <common:MyPage DataContext="{Binding Page2VM}" />
            </common:WizardPageCollection>
        </common:WizardControl.Pages>
    </common:WizardControl>
</Grid>

MainViewModel 和 PageViewModel:

public class MainViewModel
{
    public PageViewModel Page1VM
    {
        get;
        set;
    }

    public PageViewModel Page2VM
    {
        get;
        set;
    }

    public MainViewModel()
    {
        this.Page1VM = new PageViewModel("Page 1");
        this.Page2VM = new PageViewModel("Page 2");
    }
}

public class PageViewModel
{
    public string Title { get; set; }
    public PageViewModel(string title) { this.Title = title; }
}

向导控件XAML:

<Grid>
    <ContentPresenter Grid.Row="0" x:Name="contentPage"/>
</Grid>

WizardControl 代码隐藏:

public partial class WizardControl : UserControl
{
    public WizardControl()
    {
        InitializeComponent();
    }

    public WizardPageCollection Pages
    {
        get { return (WizardPageCollection)GetValue(PagesProperty); }
        set { SetValue(PagesProperty, value); }
    }

    public static readonly DependencyProperty PagesProperty =
        DependencyProperty.Register("Pages", typeof(WizardPageCollection), typeof(WizardControl), new PropertyMetadata(new WizardPageCollection(), new PropertyChangedCallback(Pages_Changed)));

    static void Pages_Changed(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        WizardPageCollection col =  e.NewValue as WizardPageCollection;
        WizardControl ctrl = obj as WizardControl;
        ctrl.contentPage.Content = col.First();
    }
}

public class WizardPageCollection : ObservableCollection<WizardPageBase> { }

public class WizardPageBase : ContentControl { }

我的页面XAML:

<Grid>
    <Label Content="{Binding Title}"   />
</Grid>

您的方法取决于 Window 的 DataContext 属性 的 value inheritance,它不适用于您的 WizardPageCollection,因为它不构成WPF 元素树。

您应该改为将 MainViewModel 创建为资源,然后通过 StaticResource:

引用它
<Window ...>
    <Window.Resources>
        <common:MainViewModel x:Key="MainViewModel"/>
    </Window.Resources>
    <Window.DataContext>
        <Binding Source="{StaticResource MainViewModel}"/>
    </Window.DataContext>
    <Grid>
        <common:WizardControl>
            <common:WizardControl.Pages>
                <common:WizardPageCollection>
                    <common:MyPage DataContext="{Binding Page1VM,
                                       Source={StaticResource MainViewModel}}"/>
                    <common:MyPage DataContext="{Binding Page2VM,
                                       Source={StaticResource MainViewModel}}"/>
                </common:WizardPageCollection>
            </common:WizardControl.Pages>
        </common:WizardControl>
    </Grid>
</Window>

@Clemens 回答解决了问题,但问题是别的,恕我直言。

  1. 当项目被添加到 WizardPageCollection 时,它也应该被添加到 LogicalTree。查看 ItemsControl 的来源以获取灵感。绝对有可能使您的绑定按原样工作。

  2. 我会在这里使用视图模型优先的方法。将页面定义为页面视图模型的集合并生成视图。最后 xaml 看起来像这样:

    <common:WizardControl PagesSource="{Binding Pages}">
        <common:WizardControl.PageTemplate>
            <DataTemplate>
                <common:MyPage DataContext="{Binding }" />
            </DataTemplate>
        </common:WizardControl.PageTemplate>
    </common:WizardControl>
    

或者,考虑您的 WizardControl 派生自 Selector class 而不是用户控件。 (选择器是来自列表框的基础 class。它有 itemssource 和选定的项目)。

    <common:WizardControl ItemsSource="{Binding Pages}" 
                          SelectedItem="{Binding SelectedPage}">
        <common:WizardControl.ItemTemplate>
            <DataTemplate>
                <common:MyPage DataContext="{Binding }" />
            </DataTemplate>
        </common:WizardControl.ItemTemplate>
    </common:WizardControl>