0
votes

I have inherited a class, MyModernWindow from Window, and added a property and dependency property called MyTitleLinks. The type is MyLinkCollection : ObservableCollection<MyLink>. In XAML, I'm trying to define the MyTitleLinks, and bind the MyLink.Command property to a property in my Window's ViewModel.

I have tried numerous ways to bind, including FindAncestor and ElementName, and I am constantly unsuccessful.

If using {Binding AboutCommand} or {Binding DataContext.AboutCommand, ElementName=mainWindow}, I get this error in the Output:

Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=AboutCommand; DataItem=null; target element is 'MylLink' (HashCode=30245787); target property is 'Command' (type 'ICommand')

If using {Binding DataContext.AboutCommand, RelativeSource={RelativeSource AncestorType={x:Type local:MyModernWindow}}},

Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='My.Namespace.MyModernWindow', AncestorLevel='1''. BindingExpression:Path=DataContext.AboutCommand; DataItem=null; target element is 'MyLink' (HashCode=35075009); target property is 'Command' (type 'ICommand')

MainWindow.xaml

<local:MyModernWindow x:Class="My.MainWindow"
                              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                              xmlns:local="clr-namespace:My.Controls"
                              IsTitleVisible="True"
                              Style="{StaticResource MyModernWindow}"
                              Title="My Window"
                              WindowStartupLocation="CenterScreen">

        <local:MyModernWindow.MyTitleLinks>
            <local:MyLink DisplayName="Support" Source="https://www.google.com/support/" />
            <local:MyLink DisplayName="About" Command="{Binding AboutCommand}" />
        </local:MyModernWindow.MyTitleLinks>
    </local:MyModernWindow>

MainWindow.xaml.cs

public partial class MainWindow : MyModernWindow
{
    public MainWindow()
    {
        InitializeComponent();

        this.DataContext = new MainWindowViewModel();
    }
}

MyLinkCollection Class

public class MyLinkCollection : ObservableCollection<MyLink>
{
}

MyLink Class

public class MyLink : DependencyObject
    {
        public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(MyLink));
        public static readonly DependencyProperty DisplayNameProperty = DependencyProperty.Register(nameof(DisplayName), typeof(string), typeof(MyLink));
        public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(Uri), typeof(MyLink));

        public Uri Source
        {
            get { return (Uri)GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }

        public string DisplayName
        {
            get { return (string)GetValue(DisplayNameProperty); }
            set { SetValue(DisplayNameProperty, value); }
        }

        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        public MyLink()
        {
            SetCurrentValue(VisibilityProperty, Visibility.Visible);
        }
    }

ViewModel

public class MainWindowViewModel
{
    public ICommand AboutCommand { get; private set; }

    public MainWindowViewModel()
    {
        this.AboutCommand = new RelayCommand(OpenAboutWindow);
    }

    private void OpenAboutWindow(object o)
    {
        ModernDialog.ShowMessage("About Screen", "About", MessageBoxButton.OK);
    }
}

What am I missing?

2
"Window's DataContext is set in the ctor of the xaml.cs file" - can you show this part? You are still missing the description of the problem. If you have binding errors (see Output window), then show them too please.Sinatr
@Sinatr, I've updated my post to answer your questions. Thank you.DaleyKD
What is MyTitleLinks ? Try to add links after you set DataContext (in code-behind) to see if that works. It should be enough to use Command="{Binding AboutCommand}" for any children element of window. It looks like MyTitleLinks is special (try e.g. making a button with same command inside window, it should work).Sinatr
@Sinatr: public MyLinkCollection MyTitleLinks {get;set;} is a property of MyModernWindow. You're right, it works fine with a Button, so why is this different?DaleyKD
I don't know what is MyModernWindow and what are you doing with that property. A wild guess you should do something after Load (where you will get properly initialized window with correct DataContext for bindings) and not in constructor.Sinatr

2 Answers

1
votes

With the help of this blog post, I figured it out. Since MyLink and MyLinkCollection aren't in the visual tree, I used a "Proxy Element" to give a context.

I gave my Window a name, created a FrameworkElement, then created a hidden ContentControl. That's all I needed.

Here's the working XAML:

<local:MyModernWindow x:Class="My.MainWindow"
                          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                          xmlns:local="clr-namespace:My.Controls"
                          x:Name="Window"
                          IsTitleVisible="True"
                          Style="{StaticResource MyModernWindow}"
                          Title="My Window"
                          WindowStartupLocation="CenterScreen">
    <local:MyModernWindow.Resources>
        <FrameworkElement x:Key="ProxyElement" DataContext="{Binding DataContext, ElementName=Window}" />
    </local:MyModernWindow.Resources>

    <ContentControl Visibility="Collapsed" Content="{StaticResource ProxyElement}"/>

    <local:MyModernWindow.MyTitleLinks>
        <local:MyLink DisplayName="Support" Source="{Binding DataContext.SupportSource, Source={StaticResource ProxyElement}}" />
        <local:MyLink DisplayName="About" Command="{Binding DataContext.AboutCommand, Source={StaticResource ProxyElement}}" />
    </local:MyModernWindow.MyTitleLinks>
</local:MyModernWindow>
0
votes

The reason for the problem is that the DataContext is not inherited from the collection nor from the MyLink item.

To have WPF automatically managing the inheritance for you without the need of a proxy element you need to add "Freezable" at each step of your tree as follows:

public class MyLinkCollection : FreezableCollection<MyLink>
{
}

and

public class MyLink : Freezable
{
     // class body
}

Xaml Behaviors Wpf(a Microsoft released project) uses the same approach to propagate the DataContext inside a Xaml defined collection without the need of additional proxies