Home > Software design >  WPF binding doesn't work when property changes
WPF binding doesn't work when property changes

Time:09-26

This is my first question here, please understand. I've spent on this problem hours of digging nothing works for me, maybe somebody will explain me this strange (for me) problem? I've made my app in WPF with MVVM

I got in MainWindow.xaml with usercontrol which loads view with binding:

<UserControl Content="{Binding CurrentView}" />

MainWindow DataContext is MainViewModel, which derives from BaseViewModel, where i set and get CurrentView from and implement INotifyPropertyChanged.

First CurrentView is LoginViewModel - it loads in constructor of MainViewModel properly and set the view (Usercontrol Loginview.xaml). And I don't understand why when I change CurrentView property from this loaded LoginViewModel (it definitely changes - I checked it and NotifyPropertyChanged raises) - my view doesn't change - it's still LoginView, but should be WelcomeView. But when I change the same property with the same code from MainViewModel - my view changes properly. Somebody could point where's an error? Is it impossible to change CurrentView property from outside of MainViewModel even it's not the part of MainViemodel but another class or what? What I'm missing here?

CODE:

    public class BaseViewModel : NotifyPropertyChanged 
    {
        private object? _currentView;
        public object? CurrentView
        {
            get { return _currentView; }
            set
            {
                _currentView = value;
                OnPropertyChanged(nameof(CurrentView));
            }
        }
    }
    public class MainViewModel : BaseViewModel
    {
        public LoginViewModel LoginViewModel { get; set; }
        public WelcomeViewModel WelcomeViewModel { get; set; }
    [..]
        public ICommand LoginCommand { get; set; } //- this works 
        public MainViewModel()
        {
            LoginViewModel = new();
            WelcomeViewModel = new();
            CurrentView = LoginViewModel;
            // COMMANDS
            LoginCommand = new RelayCommand(o => DoLogin(), o => CanLogin()); // this works
        }
        private bool CanLogin()
        {
            return true;
        }
        private void DoLogin()
        {
            CurrentView = WelcomeViewModel;
        } 
    }
    public class LoginViewModel : BaseViewModel
    {
    [...]
        public WelcomeViewModel WelcomeViewModel { get; set; }
        // COMMANDS PROPERTIES
        public ICommand LoginCommand { get; set; }
        public LoginViewModel()
        {
            WelcomeViewModel = new();
            LoginCommand = new RelayCommand(o => DoLogin(), o => CanLogin());
        }
        private bool CanLogin()
        {
            return true;
        }
        private void DoLogin()
        {
            MessageBox.Show("Login!");  // message box test showes
            // here will be some authentication
            CurrentView = WelcomeViewModel; // property CurrentView changes
            // CurrentView = new MainViewModel().WelcomeViewModel; // this way also doesn't work
        }
    }

and finally XAML from UserControl LoginView.xaml (command runs properly, property CurrentView changes, but view remains the same:

<Button
    Width="200"
    Height="50"
    Margin="10"
    Command="{Binding LoginCommand}"
    Content="Login"
    FontSize="18" />
<!--  Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, 
Path=DataContext.LoginCommand}" THIS WORKS! -->

App.xaml has:

 <DataTemplate DataType="{x:Type vievmodels:LoginViewModel}">
            <viewscontents:LoginView/>
        </DataTemplate>
 <DataTemplate DataType="{x:Type vievmodels:WelcomeViewModel}">
            <viewscontents:WelcomeView/>
        </DataTemplate>

CodePudding user response:

The mistake you make is that there are two CurrentView variables. One is in MainViewModel and the other one is in LoginViewModel. Both classes are derived from BaseViewModel but that doesn't mean they share the same instance of CurrentView. Both have a newinstance of the CurrentView variable. Meaning that only one is bound to the DataContext of the page.

What i'm unable to see it where you assign which CurrentView to the DataContext. So i'm not able to completely answering this question.

But it looks like you have 1 window filled with 2 controls. To solve this, you should create a 3rd ViewModel which only contains the CurrentView. Use this instance on a parent where the UserControl is used. And use the other ViewModels to the usercontrol itself.

CodePudding user response:

The question is: how to set DataContext .... Do this.

Main ViewModel:

    public class MainViewModel : BaseInpc
    {
        #region CurrentContent
        private object? _currentContent;
        public object? CurrentContent { get => _currentContent; set => Set(ref _currentContent, value); }

        private RelayCommand _setCurrentCommand;
        public RelayCommand SetCurrentCommand => _setCurrentCommand
            ??= new RelayCommand(content => CurrentContent = content);
        #endregion

        public LoginViewModel LoginViewModel { get; } = new LoginViewModel();
        public WelcomeViewModel WelcomeViewModel { get; } = new WelcomeViewModel();

        public MainViewModel()
        {
            CurrentContent = WelcomeViewModel;
        }
    }
    public class WelcomeViewModel: BaseInpc // Derived not from MainViewModel!
    {
        // Some Code
    }

    public class LoginViewModel: BaseInpc // Derived not from MainViewModel!
    {
        // Some Code
    }

Create an instance of MainViewModel in the application resources:

    <Application.Resources>
        <local:MainViewModel x:Key="mainVM"/>
    </Application.Resources>

In Windows XAML:

<Window ------------
        ------------
        DataContext="{DynamicResource mainVM}">
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:LoginViewModel}">
            <local:LoginViewUserControl/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type WelcomeViewModel}">
            <local:WelcomeViewUserControl/>
        </DataTemplate>
    </Window.Resources>
    <ContentControl Content="{Binding CurrentContent}"/>

An example of a button toggling CurrentContent:

    <Button Command="{Binding SetCurrentCommand, Source={StaticResource mainVM}}"
            CommandParameter="{Binding LoginViewModel, Source={StaticResource mainVM}}"/>

BaseInpc and RelayCommand classes.

  • Related