I have a custom wizard control WizardControl
deriving from UserControl
which has a dependency property called Pages
with a data type of my custom class collection called WizardPageCollection
.
The WizardControl
is hosted in a Window
with a view model called MainViewModel
and the pages of the wizard instantiated using XAML.
I am trying to bind the pages to sub-view models Page1VM
and Page2VM
declared as properties on the MainViewModel
.
The first page binding of DataContext
to Page1VM
works fine, however the binding of the second page fails with the following error message:
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')
Q. Why does the binding work on the first page but fail on the second and is there a way I can get this to work whilst still keeping the MainViewModel
declared within the DataContext
XAML tags of MainWindow
? I would prefer not to use the ViewModel as a dictionary resources as this has some implications for us which I won't go into detail about.
As suggested by a commentor, if I change the binding to use RelativeSource as follows:
<common:MyPage DataContext="{Binding DataContext.Page1VM, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
<common:MyPage DataContext="{Binding DataContext.Page2VM, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
The first binding works ok, but the second one still fails, but with a different error message (as expected):
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')
Thanks for your time!
My code listing is shown below:
MainWindow 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 and 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 code-behind:
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 { }
MyPage XAML:
<Grid>
<Label Content="{Binding Title}" />
</Grid>
Your approach depends on the value inheritance of the Window's DataContext
property, which doesn't work with your WizardPageCollection because it doesn't form a WPF element tree.
You should instead create your MainViewModel as a resource, and then reference it by 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>