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

自定义内容控件上与DataContext的WPF绑定,wpf,xaml,contentcontrol,contentpresenter,Wpf,Xaml,Contentcontrol,Contentpresenter,我有一个自定义向导控件WizardControl派生自UserControl,它有一个名为Pages的依赖属性,其数据类型为我的自定义类集合WizardPageCollection WizardControl托管在一个名为MainViewModel的视图模型的窗口中,向导的页面使用XAML实例化 我正在尝试将页面绑定到子视图模型Page1VM和Page2VM,这些模型在MainViewModel上声明为属性 DataContext到Page1VM的第一页绑定工作正常,但是第二页绑定失败,并显示以

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

WizardControl
托管在一个名为
MainViewModel
的视图模型的
窗口中,向导的页面使用XAML实例化

我正在尝试将页面绑定到子视图模型
Page1VM
Page2VM
,这些模型在
MainViewModel
上声明为属性

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

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')
问:为什么绑定在第一页工作,但在第二页失败,我有没有办法在保持
MainWindow的
DataContext
XAML标记中声明的
MainViewModel
的同时让它工作?我不希望将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}}}" />
谢谢你的时间

我的代码清单如下所示:

主窗口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>
向导控件XAML:

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

向导控制代码隐藏:

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 { }
public分部类向导控件:UserControl
{
公共控制()
{
初始化组件();
}
公共向导页面集合页面
{
获取{return(WizardPageCollection)GetValue(PagesProperty);}
set{SetValue(PagesProperty,value);}
}
公共静态只读从属属性页属性=
DependencyProperty.Register(“Pages”、typeof(WizardPageCollection)、typeof(WizardControl)、new PropertyMetadata(new WizardPageCollection()、new PropertyChangedCallback(Pages_Changed));
静态无效页面\u已更改(DependencyObject对象、DependencyPropertyChangedEventArgs e)
{
WizardPageCollection col=e.NewValue作为WizardPageCollection;
WizardControl ctrl=obj作为WizardControl;
ctrl.contentPage.Content=col.First();
}
}
公共类WizardPageCollection:ObservableCollection{}
公共类WizardPageBase:ContentControl{}
MyPage XAML:

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

您的方法取决于窗口的
DataContext
属性的类型,该属性不适用于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的答案解决了这个问题,但问题是另外一回事,伊姆霍

  • 将项添加到WizardPageCollection时,也应将其添加到LogicalTree。查看ItemsControl的来源以获取灵感。这是绝对有可能使您的绑定工作,因为他们是

  • 这里我将使用viewmodel优先的方法。将页面定义为页面视图模型的集合,并生成视图。最后,xaml将如下所示:

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

    我觉得不错。您是否尝试使用
    相对资源
    绑定到
    窗口
    ?@Mike是的,我已将DataContext绑定更改为使用相对资源(第一页绑定正常,但第二页绑定仍然失败,但错误消息略有不同)。我已经更新了帖子以反映这一点。可以在GitHub上找到源文件:您有在WizardControl中切换页面的代码吗?我没有提供此代码,因为我觉得这与绑定问题无关。但是,当我更改向导上的“当前页面”(只需设置
    contentPage.Content=newPage
    )时,该新页面上的
    DataContext
    将设置为
    NULL
    ,这在绑定失败时是有意义的。非常感谢您的回答和解释我试图实现的限制。在DataContext上声明绑定仍然允许be将元素绑定到MainViewModel,只需使用
    Property=“{binding Path=VM\u Property}”
    。感谢您的回答-尽管我提供的示例仅使用了
    MyPage
    声明的页面可以是不同的类型,但都是从
    WizardPageBase
    类派生的-但是
    DataTemplate
    不会是一个一致的视觉布局。我看到您的
    PagesSource
    ItemsSource
    绑定到VM,但我如何将它们绑定到不同类型的页面(而不仅仅是一个页面)?在这种情况下,您可以使用不同的pageviewmodel类并使用隐式数据模板(对于每个pageviewmodel类,不同的datatemplate)。如果您只想拥有一个pageviewmodel类并根据某些属性选择view,则可以使用datatemplate选择器。
    <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>
    
    <common:WizardControl PagesSource="{Binding Pages}">
        <common:WizardControl.PageTemplate>
            <DataTemplate>
                <common:MyPage DataContext="{Binding }" />
            </DataTemplate>
        </common:WizardControl.PageTemplate>
    </common:WizardControl>
    
        <common:WizardControl ItemsSource="{Binding Pages}" 
                              SelectedItem="{Binding SelectedPage}">
            <common:WizardControl.ItemTemplate>
                <DataTemplate>
                    <common:MyPage DataContext="{Binding }" />
                </DataTemplate>
            </common:WizardControl.ItemTemplate>
        </common:WizardControl>