aufty
aufty

Reputation: 417

Passing a ViewModel reference to another ViewModel (the same reference that's tied to the View)?

So I have a View with two subviews. One of the subviews is an on screen keyboard with textbox. Below that are some buttons which are part of a different subview. See below:

keyboard and buttons

When I press the keyboard buttons it types in the textbox. Both the subview with the buttons and the subview with the keyboard have their own ViewModels. My question is, how do I reference the keyboard view from the button view (so I can get the contents of the text field, for example, or clear it if the user clicks "Go Back").

I'm trying to conceptualize it, but I can't figure out how I would get the same instance of the ViewModel of the keyboard that the Main View has.

I can create a variable:

private KeyboardViewModel keyboard;

But how do I instantiate that variable with the instance that the Main View already has (so I can access those properties from the button viewmodel)?

Upvotes: 2

Views: 6409

Answers (3)

DiSaSteR
DiSaSteR

Reputation: 636

How about using ServiceLocator from Microsoft.Practices.ServiceLocation?

ServiceLocator.Current.GetInstance<ViewModelName>(); 

Upvotes: 1

cscmh99
cscmh99

Reputation: 2781

The main problem is that you misplaced your datasource in one of your ViewModel when the datasource is actually needed to be reuse in multiple View/ViewModel. What you need to do is to refactor the datasource out into a singleton instance or an seperate instance that can be injected into different ViewModels' constructor. By decoupling out the datasource from a particular ViewModel can give it freedom for different place to access.

public class DataCache
{
    private static DataCache singletonInstance;

    // You can have freedom to choose the event-driven model here
    // Using traditional Event, EventAggregator, ReactiveX, etc
    public EventHandler OnMessageChanged;

    private DataCache()
    {

    }

    public static DataCache Instance
    {
        get { return singletonInstance ?? (singletonInstance = new DataCache()); }
    }

    public string OnScreenMessage { get; set; }

    public void AddStringToMessage(string c)
    {
        if (string.IsNullOrWhiteSpace(c)) return;

        OnScreenMessage += c;
        RaiseOnMessageChanged();
    }

    public void ClearMessage()
    {
        OnScreenMessage = string.Empty;
        RaiseOnMessageChanged();
    }

    private void RaiseOnMessageChanged()
    {
        if (OnMessageChanged != null)
            OnMessageChanged(null, null);            
    }
}

public class MainViewModel : ViewModelBase
{
    private readonly MessageViewModel messageVM;
    private readonly KeyboardViewModel keyboardVM;
    private readonly ButtonsViewModel buttonsVM;

    private readonly DataCache dataCache;

    public MainViewModel()
    {
        messageVM = new MessageViewModel();
        keyboardVM = new KeyboardViewModel();
        buttonsVM = new ButtonsViewModel();
    }

    public ViewModelBase MessageViewModel { get { return messageVM; } }
    public ViewModelBase KeyboardViewModel { get { return keyboardVM;  } }
    public ViewModelBase ButtonsViewModel { get { return buttonsVM; } }
}

public class MessageViewModel : ViewModelBase
{
    private readonly DataCache dataCache = DataCache.Instance;

    public MessageViewModel()
    {
        dataCache.OnMessageChanged += RaiseMessageChanged;
    }

    private void RaiseMessageChanged(object sender, EventArgs e)
    {
        OnPropertyChanged("Message");
    }

    public string Message
    {
        get { return dataCache.OnScreenMessage; }
        set { dataCache.OnScreenMessage = value; }
    }
}

public class KeyboardViewModel : ViewModelBase
{
    private readonly DataCache dataCache = DataCache.Instance;

    private ICommand onClickButtonCommand;
    public ICommand OnClickButton
    {
        get
        {
            return onClickButtonCommand ?? (onClickButtonCommand = new RelayCommand(p => dataCache.AddStringToMessage((string)p))); 
        }
    }
}

public class ButtonsViewModel : ViewModelBase
{
    private readonly DataCache dataCache = DataCache.Instance;

    private ICommand onGoBackCommand;
    public ICommand OnGoBackButton
    {
        get
        {
            return onGoBackCommand ?? (onGoBackCommand = new RelayCommand(p => dataCache.ClearMessage()));
        }
    }
}

public class RelayCommand : ICommand
{
    #region Fields

    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;

    #endregion Fields

    #region Constructors

    public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged;

    public void RaiseCanExecuteChanged()
    {
        var handler = CanExecuteChanged;
        if (handler != null) handler(this, EventArgs.Empty);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }


    #endregion ICommand Members
}

<Window x:Class="StudentScoreWpfProj.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:StudentScoreWpfProj"        
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    mc:Ignorable="d"
    d:DataContext="{d:DesignInstance Type=local:MainViewModel,IsDesignTimeCreatable=True}"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <local:MessgaeView DataContext="{Binding MessageViewModel}" />
    <local:KeyboardView Grid.Row="1" DataContext="{Binding KeyboardViewModel}" />
    <local:ButtonsView Grid.Row="2" DataContext="{Binding ButtonsViewModel}" />
</Grid>

<UserControl x:Class="StudentScoreWpfProj.ButtonsView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:local="clr-namespace:StudentScoreWpfProj"
         mc:Ignorable="d" 
         d:DataContext="{d:DesignInstance Type=local:ButtonsViewModel,IsDesignTimeCreatable=True}"
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <StackPanel Orientation="Horizontal">
        <Button Content="GoBack" Command="{Binding OnGoBackButton}"></Button>
        <Button Content="Continue"></Button>
    </StackPanel>
</Grid>

<UserControl x:Class="StudentScoreWpfProj.KeyboardView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:local="clr-namespace:StudentScoreWpfProj"
         mc:Ignorable="d" 
         d:DataContext="{d:DesignInstance Type=local:KeyboardViewModel,IsDesignTimeCreatable=True}"
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
     <StackPanel Orientation="Horizontal">
        <Button Content="A" Command="{Binding OnClickButton}" CommandParameter="A"></Button>
        <Button Content="B" Command="{Binding OnClickButton}" CommandParameter="B"></Button>
        <Button Content="C" Command="{Binding OnClickButton}" CommandParameter="C"></Button>
    </StackPanel>
</Grid>

<UserControl x:Class="StudentScoreWpfProj.MessgaeView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:local="clr-namespace:StudentScoreWpfProj"
         mc:Ignorable="d" 
         d:DataContext="{d:DesignInstance Type=local:MessageViewModel,IsDesignTimeCreatable=True}"
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <TextBox Text="{Binding Message}"/>
</Grid>

Upvotes: 5

Noctis
Noctis

Reputation: 11783

You could do several things ...

You could create a static instance for easy access, and expose what you want on it (not recommended, read comments).

You could use dependency injection, so your other viewmodel will take the keyboard viewmodel as a parameter (please have a look at my other answer, it'll get you started quicly).

You could use a messenger to help you talk between them as well. most mvvm frameworks will have some ( have a look at this SO question, and at this code project article to get you started. They are specifically for MVVM light, but they'll help you understand the concept) .

Upvotes: 2

Related Questions