Search code examples
c#wpfxamldatacontexticommand

DataContext on CommandParameter differs from DataContext on Command itself


This question is related to my previous question here: Predicate won't validate parameter correctly

At first some information on my Models:

BaseViewModel:

public abstract class BaseViewModel : INotifyPropertyChanged 
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _name;
    public string Name
    {
        get 
        {
            return _name;
        }
        set 
        {
            if (Name != value) 
            {
                _name = value;
                OnPropertyChanged("Name");
            }
        }
    }

    private BaseViewModel _homePage;
    public BaseViewModel HomePage 
    {
        get 
        {
            return _homePage;
        }
        set 
        {
            if (HomePage != value) 
            {
                _homePage = value;
                OnPropertyChanged("HomePage");
            }
        }
    }

    public void OnPropertyChanged(string propertyName) 
    {
        PropertyChangedEventHandler temp = PropertyChanged;
        if (temp != null) 
        {
            temp(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Problem:

<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
        CommandParameter="{Binding HomePage}"/>

As you can see in the BaseViewModel, the Properties "Name" and "HomePage" are defined in the same class so they should be accessible if the DataContext is a ViewModel which derives from "BaseViewModel". At first I lost my mind because nothing seemed to work - the output window said that the value of "HomePage" could not be retrieved - other that the Name which got bound properly every time.

After I nearly gave up I named my View "Test" and tried to redirect the CommandProperty binding along an ElementName - and it worked:

<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
        CommandParameter="{Binding DataContext.HomePage, ElementName=Test}"/>

But why? Why differs the DataContext between the Name Property which could be bound without assigning an ElementName and HomePage which requires the ElementName?

Update 1:

MainViewModel : BaseViewModel

private RelayCommand _command;
    public RelayCommand ChangePageCommand {
        get {
            return _command ?? (_command = new RelayCommand(p => ChangeViewModel((BaseViewModel)p), x => {
                return x is BaseViewModel;
            }));
        }
    }

public void ChangeViewModel(BaseViewModel viewModel) {
        CurrentPageViewModel = viewModel;
    }

Update 2:

<Window.Resources>
    <DataTemplate DataType="{x:Type home:HomeViewModel}">
        <home:Home/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type ua:UserAdministrationViewModel}">
        <ua:UserAdministration/>
    </DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentPageViewModel}"/>

Update 3:

Now it's getting really strange - I tried to add an TextBlock and bind it directly to HomePage.Text - and it works.

<Button Height="50" Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
        CommandParameter="{Binding DataContext.HomePage, ElementName=Test}"/>
        <TextBlock Text="{Binding HomePage.Name}"/>

So why can't I directly access HomePage when binding to the CommandParameter but binding a TextBlock's Text Property directly to HomePage.Name works?

Update 4:

The Output Windows sais:

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=Name; DataItem=null; target element is 'Button' (Name=''); target property is 'Content' (type 'Object')

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=DataContext.ChangePageCommand; DataItem=null; target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=HomePage; DataItem=null; target element is 'Button' (Name=''); target property is 'CommandParameter' (type 'Object')

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=HomePage.Name; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

But everything except the CommandParameter gets bound successfully. Is it possible that the CommandParameter (and the underlaying CanExecute-Method) won't get re-validated once the binding is refreshed? Maybe the binding fails at first but every binding except the CommandParameter gets refreshed and revalidated. But how could I solve this issue?


Solution

  • I'm proud to announce that I've solved this silly problem:

    I assume that the RelativeSource I'm using to redirect my CommandBinding to the Window's ViewModel breaks the DataContext of all following Properties (!Not Controls).

    Not working:

    <Button Grid.Row="0" 
                Grid.Column="0" 
                Height="50" 
                Content="{Binding PreviousPageViewModel.Name}"
                Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType=Window}}"
                CommandParameter="{Binding PreviousPageViewModel}" />
    

    Working:

    <Button Grid.Row="0" 
                Grid.Column="0" 
                Height="50" 
                Content="{Binding PreviousPageViewModel.Name}" 
                CommandParameter="{Binding PreviousPageViewModel}" 
                Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
    

    So in this case the order of Properties is important.

    Note: The DataContext for all following Controls is correct.