1
votes

I have the below visibility binding that is throwing:

System.Windows.Data Error: 40 : BindingExpression path error: 'Vis' property not found on 'object' ''LoginViewModel' (HashCode=22943289)'. BindingExpression:Path=Vis; DataItem='LoginViewModel' (HashCode=22943289); target element is 'Login' (Name=''); target property is 'Visibility' (type 'Visibility')

Can't see what I've done wrong, the property does exist in the MainViewModel. Maybe I'm going about showing and hiding this the wrong way.

<Window x:Class="Bt.MainWindow"
        xmlns:vm="clr-namespace:Bt"
        xmlns:ctrls="clr-namespace:Bt.Controls">

    <Window.DataContext>
        <vm:MainViewModel x:Name="MWin" />
    </Window.DataContext>

    <Grid>
        <ctrls:Login Visibility="{Binding Vis}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></ctrls:Login>
    </Grid>
</Window>

ViewModel:

namespace Bt
{
    class MainViewModel : INotifyPropertyChanged
    {
        public MainViewModel()
        {
            Vis = "Collapsed";
        }

        private string _vis = "Collapsed";
        public string Vis
        {
            get { return _vis; }
            set
            {
                    _vis = value;
                    RaisePropertyChanged("Vis");
            }
        }
    }
}


[EDIT] Capturing inside the User Control, when the User Control's visibility is changed in the Main window.

I realize that the converter is not being called correctly, so may need some help there as well. As for the rest hopefully you can see what I'm trying to achieve.

View:

<UserControl x:Class="Bt.Controls.Login" 
             xmlns:app="clr-namespace:Bt" 
             xmlns:viewmodel="clr-namespace:Bt.Controls"
             mc:Ignorable="d"
             Visibility="{Binding Visi,Converter={StaticResource BooleanToVisibilityConverter}}"
            >
</UserControl>

View Model:

namespace Bt.Controls
{
    class LoginViewModel : INotifyPropertyChanged
    {
        public LoginViewModel(){}

        private bool _visi = true;
        public bool Visi
        {
            get { return _visi; }
            set
            {
                _visi = value;
                RaisePropertyChanged("Visi");
                MessageBox.Show("Visi set");
                reset_timer(_visi);
            }
        }
    }

    [ValueConversion(typeof(bool), typeof(Visibility))]
    public class VisibilityConverter : IValueConverter
    {
        public const string Invert = "Invert";

        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            if (targetType != typeof(Visibility))
                throw new InvalidOperationException("The target must be a Visibility.");

            bool? bValue = (bool?)value;

            if (parameter != null && parameter as string == Invert)
                bValue = !bValue;

            return bValue.HasValue && bValue.Value ? Visibility.Visible : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            return ((value is Visibility) && (((Visibility)value) == Visibility.Visible));
        }
        #endregion
    }
}
2

2 Answers

3
votes

As evident from error BindingEngine is looking for property Vis in LoginViewModel and not in MainViewModel. (You must have set DataContext for your Login UserControl to LoginViewModel).

You need to get Window's DataContext which you can get using RelativeSource:

<ctrls:Login Visibility="{Binding DataContext.Vis,
       RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>

Also you should create Vis as bool and use BooleanToVisibility converter in your binding.

<Grid>
    <Grid.Resources>
       <BooleanToVisibilityConverter x:Key="BooelanToVisibilityConverter"/>
    </Grid.Resources>
    <ctrls:Login Visibility="{Binding DataContext.Vis,
                           RelativeSource={RelativeSource Mode=FindAncestor,
                                                          AncestorType=Window}}",
                         Converter={StaticResource BooelanToVisibilityConverter}
                 HorizontalAlignment="Stretch" 
                 VerticalAlignment="Stretch"/>
</Grid>

I would discourage use of Visibility property in ViewModel because it's View thing which should not be there in ViewModel. Having bool is perfectly fine which you can always convert using converter.

0
votes

Keep Vis as type of Visibility

class MainViewModel : INotifyPropertyChanged
{
    public MainViewModel()
    {

    }

    private Visibility _vis = Visibility.Collapsed;
    public Visibility Vis
    {
        get { return _vis; }
        set
        {
            _vis = value;
            RaisePropertyChanged("Vis");
        }
    }
}

Also specify the Source for the binding,

<Window x:Class="Bt.MainWindow"
    xmlns:vm="clr-namespace:Bt"
    xmlns:ctrls="clr-namespace:Bt.Controls">

    <Window.Resources>
        <vm:MainViewModel x:Key="MWin" />           
    </Window.Resources>

    <Grid>
        <ctrls:Login Visibility="{Binding Vis, RelativeSource={StaticResource MWin}}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></ctrls:Login>
    </Grid>
</Window>

As Rohit says, you could use BooleanToVisibility instead of changing the property as Visibility..