How to Call Functions in a Main View Model from Other View Models

How to call functions in a main view model from other view models?


The delegate method used in this and the linked answer can be used in any parent-child relationship and in either direction. That includes from a child view model to a parent view model, a Window code behind to the code behind of a child Window, or even pure data relationships without any UI involved. You can find out more about using delegate objects from the Delegates (C# Programming Guide) page on MSDN.

I just answered a similar question to this earlier today. If you take a look at the Passing parameters between viewmodels post, you'll see that the answer involves using delegate objects. You can simply replace these delegates (from the answer) with your method(s) and it will work in the same way.

Please let me know if you have any questions.


UPDATE >>>

Yes, sorry I completely forgot you wanted to call methods instead... I've been working on too many posts tonight. So still using the example from the other post, just call your method in the ParameterViewModel_OnParameterChange handler:

public void ParameterViewModel_OnParameterChange(string parameter)
{
// Call your method here
}

Think of the delegate as being your path back to the parent view model... it's like raising an event called ReadyForYouToCallMethodNow. In fact, you don't even need to have an input parameter. You could define your delegate like this:

public delegate void ReadyForUpdate();

public ReadyForUpdate OnReadyForUpdate { get; set; }

Then in the parent view model (after attaching the handler like in the other example):

public void ChildViewModel_OnReadyForUpdate()
{
// Call your method here
UpdateDisplay();
}

As you have multiple child view models, you could define the delegate in another class that they both have access to. Let me know if you have any more questions.


UPDATE 2 >>>

After reading your last comment again, I've just thought of a much simpler method that might achieve what you want... at least, if I understand you correctly. It is possible for you to Bind directly from your child views to your parent view model. For instance, this would allow you to Bind a Button.Command property in a child view to an ICommand property in your parent view model:

In TreeViewView:

<Button Content="Click Me" Command="{Binding DataContext.ParentCommand, 
RelativeSource={RelativeSource AncestorType={x:Type MainWindow}}}" />

This of course assumes that an instance of the parent view model in question is set as the DataContext of the MainWindow.

Call a function from another ViewModel

There are multiple ways to accomplish what you want. I'll post two approaches that are very common when working in an MVVM pattern

Event based approach:

 public MainViewModel()
{
LoginVM = new LoginViewModel(SharedData);
LoginVM.PropertyChanged += LoginVM_PropertyChanged;
InitialVM = new InitialViewModel(SharedData);
FirstVM = new FirstViewModel(SharedData);
SecondVM = new SecondViewModel(SharedData);

//ActualView = InitialVM;
ActualView = LoginVM;
}

private void LoginVM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(sender.GetType() == typeof(LoginViewModel) && e.PropertyName == "MessageInfo")
{
var loginVM = (LoginViewModel)sender;
if (loginVM.MessageInfo == "OK")
{
ActualView = InitialVM;
}
}
}

Cunstructor Injection:

        private Action _loginAction;
public LoginViewModel(DataManager sharedData, Action loginAction )
{
_DataManager = sharedData;
DoLoginCmd = new RelayCommand(param => DoLogin(), canExec => (!string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password)));
_loginAction = loginAction;
}

public void DoLogin()
{
if (Username == "admin" && Password == "password")
{
_DataManager.User.Name = "Administrator";
_DataManager.User.Mail = "admin@company.com";

MessageInfo = "Login OK!... How to redirect??";
_loginAction.Invoke();
}
else
{
MessageInfo = "Username or Password incorrect!";
}

}

and

 public MainViewModel()
{
LoginVM = new LoginViewModel(SharedData, () => ActualView = InitialVM);
InitialVM = new InitialViewModel(SharedData);
FirstVM = new FirstViewModel(SharedData);
SecondVM = new SecondViewModel(SharedData);
//ActualView = InitialVM;
ActualView = LoginVM;
}

Side note: to put a property "ActualView" on the BaseViewModel is a rather odd choice

How to access a function from a viewModel in another viewModel

This is usually a symptom of bad architecture.

If StorageViewModel is acting like a Repository it should not extend ViewModel. If it doesn't have connections to UI you can convert it to a repository class and that would solve your problem because it would just become an injectable singleton.

If StorageViewModel is connected to a Fragment (for example) you should take a reference to both viewmodels and pass data between them from the UI layer.

Something like:

class StorageFragment : Fragment {
private val storageViewModel: StorageViewModel by viewModels()
private val mainActivityViewModel: MainViewModel by activityViewModels()

//....

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//you can do this if the song saving is a UI related thing
//just have playOrToggleSong accept a function as parameter
//as success callback
button.setOnClickListener {
mainActivityViewModel.playOrToggleSong(...) {
storageViewModel.saveLastPlayedSong(param)
}
}
}
}

Calling a method from a different ViewModel

Using Ed's idea in the question comments I did the following.

class ShellViewModel : Conductor<object>
{
public void Open_ToolInventory()
{
ActivateItem(new TIViewModel());
}
public void ViewProblemReport()
{
WindowManager wm = new WindowManager();
VPRViewModel vprvm = new VPRViewModel();
wm.ShowDialog(vprvm);
}
}

Was changed to:

class ShellViewModel : Conductor<object>
{
TIViewModel tivm = new TIViewModel();
VPRViewModel vprvm = new VPRViewModel();

public void OpenToolInventory()
{
ActivateItem(tivm);
}
public void ViewProblemReport()
{
WindowManager wm = new WindowManager();
wm.ShowDialog(vprvm);
tivm.PopulateToolInventory();
}
}

This runs the targeted method after the dialog is closed updating the tool inventory to reflect all the solved problems at once. You're the best, Ed!

Calling a method in one viewmodel from a different viewmodel

If you use MVVM Light, I assume that you have ViewModelLocator instance in your App.xaml resources defined like below.

<vm:ViewModelLocator xmlns:vm="clr-namespace:WPApp.ViewModel" x:Key="Locator" />

In your settings view code behind:

private async void ContactsSortParametersListPicker_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
await ((ViewModelLocator)App.Current.Resources["Locator"]).OtherViewModel.LoadDirectory();
}

how to call method defined in view from view model


As @tabby suggested i would use public property in viewmodel and bind
to CircleProgressBar using visibility converter. For binding opactiy you can directly bind it.

Viewmodel . Assuming you have used INotifyPropertyChanged Interface. Wrap the property in INPC.

public bool IsBusy;
public int Opacity;


SomeAction()
{
IsBusy = true;
Thread.Sleep(5000);
IsBusy = false;
Opactiy = 1;

}

Converter

 public BooleanVisibiltyConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool flag = false;
if (value is bool)
{
flag = (bool)value;
}
else if (value is bool?)
{
bool? nullable = (bool?)value;
flag = nullable.HasValue ? nullable.Value : false;
}
return (flag ? Visibility.Visible : Visibility.Collapsed);
}
}

XAML

Add the converter to the window resource before consuming it.

<CircularProgressBar Opacity="{Binding Opactiy}"  Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"

Calling a function in a Window from a different Window's View Model


Use MVVM Light Messenger

> http://dotnetpattern.com/mvvm-light-messenger
public class ViewModelA : ViewModelBase
{
public void SearchCommandMethod()
{
MessengerInstance.Send<NotificationMessage>(new NotificationMessage("notification message"));
}
}

Jesse Liberty of Microsoft has a great concrete walk through on how to make use of the messaging within MVVM Light. The premise is to create a class which will act as your message type, subscribe, then publish.

public class GoToPageMessage
{
public string PageName { get; set; }
}
This will essentially send the message based on the above type/class...

private object GoToPage2()
{
var msg = new GoToPageMessage() { PageName = "Page2" };
Messenger.Default.Send<GoToPageMessage>( msg );
return null;
}

Now you can register for the given message type, which is the same class defined above and provide the method which will get called when the message is received, in this instance ReceiveMessage.

Messenger.Default.Register<GoToPageMessage>
(
this,
( action ) => ReceiveMessage( action )
);

private object ReceiveMessage( GoToPageMessage action )
{
StringBuilder sb = new StringBuilder( "/Views/" );
sb.Append( action.PageName );
sb.Append( ".xaml" );
NavigationService.Navigate(
new System.Uri( sb.ToString(),
System.UriKind.Relative ) );
return null;
}


Related Topics



Leave a reply



Submit