A.Pissicat
A.Pissicat

Reputation: 3295

Create a ViewModel with sub ViewModel

Is there a proper way to create a C#/WPF ViewModel containing subViewModel ?

Objective is:

I have a MainWindow. That window is use to read/create images. There is a button on that windows who switch between 2 UserControl one with IHM used to read image, the other one used to create.

The MainWindow has a MainWindowViewModel with :

I want that both UserControls can acces to MainWindowViewModel field/properties and have they own commands.

Construction will be something like this:

public partial class ReadUserControl : UserControl
{
    public ReadUserControl()
    {
        InitializeComponent();
        DataContext = MainViewModel.ReadViewModel;
    }
}

public partial class CreateUserControl : UserControl
{
    public CreateUserControl()
    {
        InitializeComponent();
        DataContext = MainViewModel.CreateViewModel;
    }
}

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = MainViewModel;
    }
}

For example, if a MainViewModel contain a field ImageWidth setting ImageWidth in CreateUserControl change the value for ReadUserControl.

I hope to have been clear, I don't know how design my MainViewModel to achieve this result

EDIT1:

I've created the MainWindowViewModel as a Singleton but i'm still unable to get MainViewModel.CreateViewModel and MainViewModel.ReadViewModel

public class MainWindowViewModel : ViewModelBase
{   
    private static MainWindowViewModel _instance = null;
    public static MainWindowViewModel Instance
    {
        get
        {
            if (_instance == null)
                _instance = new MainWindowViewModel();
            return _instance;
        }
    }
    private MainWindowViewModel()
        : base()
    {
    }

    #region CreateViewModel
    /* How to create ? */
    #endregion
    #region ReadViewModel
    /* How to create ? */
    #endregion
}

Upvotes: 1

Views: 1500

Answers (2)

lokusking
lokusking

Reputation: 7456

Your example will work. At least if you have made your MainViewModel a Singleton.

A more professional approach might be an Constructor-Injection like this.

public partial class ReadUserControl : UserControl
{
    public ReadUserControl(MainViewModel vm)
    {
        InitializeComponent();
        DataContext = vm.ReadViewModel;
    }
}

With such DependencyInjections you can achieve a higher level of abstraction, since your UserControls can be generalized. (They will all have the same Constructor)

On the other hand, you give every such UserControl the ability, to manipulate the MainViewModel, not aware of side-effects.

In your special case, it would be more safe, to pass only the needed parameters to the UserControl, instead of giving them a bunch of informations, they will never need.

public partial class ReadUserControl : UserControl
    {
        public ReadUserControl(Icommand command, int imageLength, AppParams appParams)
        {
            InitializeComponent();
            ...
            // Do with your Constructorparameters what ever you have to
        }
}

Edit:

Here a small, dumb implementation of how it could be done:

Code

 public class MainViewModel : INotifyPropertyChanged {
    private INotifyPropertyChanged _selectedViewModel;

    public MainViewModel() {
      var cmd = new RelayCommand(x => {
        MessageBox.Show("HelloWorld");
      }, x => true);
      this.RVM = new ReadViewModel(cmd);
      this.WVM = new WriteViewModel(cmd);
      this.SelectedViewModel = WVM;
    }

    private ICommand _switchViewModelCommand;

    public ICommand SwitchViewModelCommand => this._switchViewModelCommand ?? (this._switchViewModelCommand = new RelayCommand(x => {
      if (this.SelectedViewModel == RVM) {

        this.SelectedViewModel = WVM;
        return;
      }
      this.SelectedViewModel = RVM;
    }));

    public INotifyPropertyChanged SelectedViewModel {
      get {
        return this._selectedViewModel;
      }
      set {
        if (Equals(value, this._selectedViewModel))
          return;
        this._selectedViewModel = value;
        this.OnPropertyChanged();
      }
    }

    public ReadViewModel RVM {
      get; set;
    }

    public WriteViewModel WVM {
      get; set;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
      this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
  }

  public class ReadViewModel : INotifyPropertyChanged {
    public ReadViewModel(ICommand sayHelloCommand) {
      this.HelloCommand = sayHelloCommand;
    }

    public ICommand HelloCommand {
      get;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
      this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
  }

  public class WriteViewModel : INotifyPropertyChanged {

    public WriteViewModel(ICommand sayHelloCommand) {
      this.HelloCommand = sayHelloCommand;
    }

    public ICommand HelloCommand {
      get;
    }

    public ICommand HelloMoonCommand => new RelayCommand(x => { MessageBox.Show("Hello Moon"); });

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
      this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    }
  }

XAML

 <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>

    <Grid Height="200">
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <ContentControl Content="{Binding SelectedViewModel, UpdateSourceTrigger=PropertyChanged}">
            <ContentControl.Resources>
                <DataTemplate DataType="{x:Type local:ReadViewModel}">
                    <StackPanel>
                        <Button Content="Say Hello world" Command="{Binding HelloCommand}"></Button>
                    </StackPanel>
                </DataTemplate>
                <DataTemplate DataType="{x:Type local:WriteViewModel}">
                    <StackPanel>
                        <Button Content="Say Hello world" Command="{Binding HelloCommand}"></Button>
                        <Button Content="Say Hello Moon" Command="{Binding HelloMoonCommand}"></Button>
                    </StackPanel>
                </DataTemplate>
            </ContentControl.Resources>
        </ContentControl>
        <Button Content="Switch VM" Command="{Binding SwitchViewModelCommand}" Grid.Row="1"/>
    </Grid>

Upvotes: 1

Steve
Steve

Reputation: 11973

You can pass in the MainViewModel as DataContext for your user control and set the data context of elements as Read/Create model

something like

<Grid> <!--using MainWindowViewModel as data context-->
    <Grid DataContext="{Binding Path=CreateViewModel}"> <!--using CreateViewModel as data context-->
          .....
    </Grid>
<Grid>

Upvotes: 0

Related Questions