0

My idea is to have a window with 2 content controls changing to different views. For example, my app may start with a welcome image in one content control and enter/exit button in other. On enter button click, I want to change the welcome view to some other view and the enter/exit buttons to 3 other buttons.

My current problem is, trying to change the welcome view by clicking the enter button does not seem to work if I put it in a user control and host it in content control. My guess is because I am actually creating a new instance of my MainViewModel instead of referencing the existing one, but I am not entirely sure.

How can I make it so, when I click on my button in BottomPanelTwoButtonsView my HomeView changes to TestView?

There are 4 views in total:

HomeView - the welcome screen

TestView - some other view

BottomPanelTwoButtonsView - enter/exit buttons

BottomPanelThreeButtonsView - some 3 other buttons

Here's the basic code I have:

App.xaml.cs

public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            MainWindow app = new MainWindow();
            MainViewModel context = new MainViewModel();
            app.DataContext = context;
            app.Show();
        }
    }

MainWindow.xaml

<Window x:Class="MyApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:localViewModels="clr-namespace:MyApp.ViewModels"
        xmlns:localViews="clr-namespace:MyApp.Views"
        mc:Ignorable="d"
        Name="RootWindow"
        WindowStyle="None" ResizeMode="NoResize"
        Topmost="True"
        WindowStartupLocation="CenterScreen"
        WindowState="Maximized"
        Background="{StaticResource BackgroundSolidColorBrush}"
        ToolBarTray.IsLocked="True"
        NumberSubstitution.Substitution="European"
        Title="MyApp" Height="1920" Width="1080">
    <Window.Resources>
        <DataTemplate DataType="{x:Type localViewModels:HomeViewModel}">
            <localViews:HomeView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type localViewModels:TestViewModel}">
            <localViews:TestView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type localViewModels:BottomPanelTwoButtonsViewModel}">
            <localViews:BottomPanelTwoButtons />
        </DataTemplate>
        <DataTemplate DataType="{x:Type localViewModels:BottomPanelThreeButtonsViewModel}">
            <localViews:BottomPanelThreeButtons />
        </DataTemplate>
    </Window.Resources>
    <DockPanel Background="White">           
        <ContentControl Content="{Binding CurrentBottomPanelViewModel}" DockPanel.Dock="Bottom" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Height="96" Width="1080">                
        </ContentControl>
        <ContentControl Content="{Binding CurrentPageViewModel}" HorizontalAlignment="Stretch" VerticalAlignment="Top" HorizontalContentAlignment="Stretch" Height="1824" Width="1080">               
        </ContentControl>
    </DockPanel>
</Window>

MainViewModel.cs

public class MainViewModel : ObservableObject
    {
        #region Fields

        private ICommand _changePageCommand;
        private IPageViewModel _currentPageViewModel;
        private IBottomPanelViewModel _currentBottomPanelViewModel;
        private ObservableCollection<IPageViewModel> _pageViewModels;

        #endregion

        public MainViewModel()
        {

            CurrentPageViewModel = new HomeViewModel();
            CurrentBottomPanelViewModel = new BottomPanelTwoButtonsViewModel();
            //CurrentBottomPanelViewModel = new BottomPanelThreeButtonsViewModel();

            PageViewModels.Add(new ScreeningsScheduleViewModel());
            PageViewModels.Add(new TestViewModel());
            PageViewModels.Add(CurrentPageViewModel);
        }

        #region Properties / Commands

        public ICommand ChangePageCommand
        {
            get
            {
                if (_changePageCommand == null)
                {
                    _changePageCommand = new RelayCommand(
                        p => ChangeViewModel((IPageViewModel)p),
                        p => p is IPageViewModel);
                }

                return _changePageCommand;
            }
        }

        public ObservableCollection<IPageViewModel> PageViewModels
        {
            get
            {
                if (_pageViewModels == null)
                {
                    _pageViewModels = new ObservableCollection<IPageViewModel>();
                }

                return _pageViewModels;
            }
            set
            {
                _pageViewModels = value;
                OnPropertyChanged("PageViewModels");
            }
        }

        public IPageViewModel CurrentPageViewModel
        {
            get => _currentPageViewModel;
            set
            {
                if (_currentPageViewModel != value)
                {
                    _currentPageViewModel = value;
                    OnPropertyChanged("CurrentPageViewModel");
                }
            }
        }

        public IBottomPanelViewModel CurrentBottomPanelViewModel
        {
            get => _currentBottomPanelViewModel;
            set
            {
                if (_currentBottomPanelViewModel != value)
                {
                    _currentBottomPanelViewModel = value;
                    OnPropertyChanged("CurrentBottomPanelViewModel");
                }
            }
        }

        #endregion

        #region Methods

        private void ChangeViewModel(IPageViewModel viewModel)
        {                
            if (!PageViewModels.Contains(viewModel))
            {
                PageViewModels.Add(viewModel);
            }

            CurrentPageViewModel = PageViewModels
                .FirstOrDefault(vm => vm == viewModel);    
        }

        #endregion
    }

BottomPanelTwoButtonsView.xaml

<UserControl x:Class="MyApp.Views.BottomPanelTwoButtons"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:mvm="clr-namespace:MyApp"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="1080">
    <UserControl.DataContext>
        <mvm:MainViewModel/>
    </UserControl.DataContext>
    <Grid Height="96" Background="White" Width="1080">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Grid.Column="0" 
                Content="1"
                Command="{Binding DataContext.ChangePageCommand, Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type mvm:MainViewModel}}, Mode=TwoWay}"
                CommandParameter="{Binding PageViewModels[2]}"
                IsEnabled="True"
                Style="{StaticResource RoundCornerButtonCyanFilledBig}" 
                />
        <Button Grid.Column="1" Style="{StaticResource RoundCornerButtonMagentaFilledBig}"/>
    </Grid>
</UserControl>

IPageViewModel.cs

public interface IPageViewModel
    {
        string Name { get; }
    }

ObservableObject.cs

public abstract class ObservableObject : INotifyPropertyChanged
    {
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public virtual void VerifyPropertyName(string propertyName)
        {        
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                string msg = "Invalid property name: " + propertyName;

                if (!ThrowOnInvalidPropertyName)
                {
                    Debug.Fail(msg);
                }
                else
                {
                    throw new Exception(msg);
                }
            }
        }

        protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

        public virtual void RaisePropertyChanged(string propertyName)
        {
            VerifyPropertyName(propertyName);
            OnPropertyChanged(propertyName);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            this.VerifyPropertyName(propertyName);

            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }
    }

RelayCommand.cs

public class RelayCommand : ICommand
    {
        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;

        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            _execute = execute ?? throw new ArgumentNullException("execute");
            _canExecute = canExecute;
        }

        [DebuggerStepThrough]
        public bool CanExecute(object parameters)
        {
            return _canExecute == null ? true : _canExecute(parameters);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameters)
        {
            _execute(parameters);
        }
    }
Yuropoor
  • 354
  • 2
  • 17
  • In general, a UserControl should never have an own ViewModel. You would instead have a DataTemplate that contains your UserControl, which has its DataType set to the type of the view model data that the UserControl is supposed to display. Then the UserControl would expose dependency properties that are bound to the properties of the view model data object. See [Data Templating Overview](https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/data-templating-overview). – Clemens Jul 05 '19 at 09:20
  • @Clemens I am following this -> https://stackoverflow.com/questions/12206120/window-vs-page-vs-usercontrol-for-wpf-navigation/12216068#12216068 and this -> https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/ - but maybe I got it all wrong, new to WPF. – Yuropoor Jul 05 '19 at 10:18
  • None of these seem to tell you that your UserControls should have own view models. As far as I remember, Rachel knows that fact very well. – Clemens Jul 05 '19 at 10:31
  • I first made the ButtonView as ItemControl with template - but I didn't know how to make it so that it once displays 2 buttons, once 2 buttons with some text between - so I thought about makinga view. But as said, I may have gotten it all wrong. – Yuropoor Jul 05 '19 at 10:36

1 Answers1

0

I guess that your problem is within the BottomPanelTwoButtonsView.xaml

There you have the following lines:

<UserControl.DataContext>
    <mvm:MainViewModel/>
</UserControl.DataContext>

So you're overwriting your datacontext which is defined in the Window.Resources of the MainWindow

Tomtom
  • 9,087
  • 7
  • 52
  • 95