【问题标题】:WPF Binding with DataContext on Custom Content ControlWPF 与自定义内容控件上的 DataContext 绑定
【发布时间】:2015-11-04 19:38:10
【问题描述】:

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

WizardControl 托管在 Window 中,视图模型名为 MainViewModel,向导页面使用 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')

问。为什么绑定在第一页上工作,但在第二页上失败,有没有办法让这个工作,同时仍然保持在 DataContextMainWindowDataContext 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}}}" />

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

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; }
}

WizardControl 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>

【问题讨论】:

  • 对我来说看起来不错。您是否尝试过使用RelativeSource 绑定来绑定到Window
  • @Mike 是的,我已将 DataContext 绑定更改为使用 RelativeSource(第一个页面绑定工作正常,但第二个页面绑定仍然失败,但错误消息略有不同)。我已经更新了帖子以反映这一点。
  • 源文件可以在 GitHub 找到:github.com/dotnetdeveloper53/WizardControlBinding
  • 你有在 WizardControl 中切换页面的代码吗?
  • 我没有提供此代码,因为我觉得这与绑定问题无关。但是,当我更改向导上的“当前页面”(只需设置contentPage.Content = newPage)时,该新页面上的DataContext 设置为NULL - 这在绑定失败时是有意义的。

标签: wpf xaml contentcontrol contentpresenter


【解决方案1】:

您的方法取决于 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>

【讨论】:

  • 非常感谢您的回答并解释了我试图实现的目标的局限性。在 DataContext 上声明绑定仍然允许将元素绑定到 MainViewModel,只需使用 Property="{Binding Path=VM_Property}"
【解决方案2】:

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

  1. 当项目被添加到 WizardPageCollection 时,它也应该被添加到 LogicalTree。查看 ItemsControl 的来源以获得灵感。完全可以让您的装订工作原封不动。

  2. 我会在这里使用 viewmodel 第一种方法。将页面定义为页面视图模型的集合并生成视图。最后,xaml 将如下所示:

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

或者,考虑您的 WizardControl 派生自 Selector 类而不是用户控件。 (选择器是列表框的基类。它有 itemssource 和 selected item)。

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

【讨论】:

  • 感谢您的回答 - 尽管我提供的示例仅使用了 MyPage,但声明的页面可能是不同的类型,但都派生自 WizardPageBase 类 - DataTemplate 只是赢了不是一种一致的视觉布局。我看到您的 PagesSourceItemsSource 正在绑定到虚拟机,但我如何将它们绑定到不同类型的页面 - 而不仅仅是一个?
  • 在这种情况下,您可以使用不同的 pageviewmodel 类并使用隐式数据模板(foreach pageviewmodel class different datatemplate)。如果您只想拥有一个 pageviewmodel 类并根据某些属性选择视图,则可以使用数据模板选择器。
猜你喜欢
  • 2014-06-24
  • 2023-04-10
  • 2013-03-15
  • 1970-01-01
  • 1970-01-01
  • 2015-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多