自定义内容控件上与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>