How to call method in window (.xaml.cs) from viewmodel (.cs) without introducing new references in wpf
Asked Answered
M

2

19

I'm looking for a simple way to call a method in my Main Window, but I want to call it from my View Model. Basically, I'm looking for some king of "this.parent" sort of thing to put in the View Model to reference the Main Window.

Or, if you want to check out the reason I want to do this and tell me another way to go about my problem:

I'm working with an app that constantly gets information fed to it. In the viewmodel, the information is processed. I want to make a notification every time a piece of information comes in that satisfies some qualification.

Initially, I had a dictionary in the viewmodel that stored info about that information, and I accessed that dictionary in the MainWindow so that I could make the window flash and send other notifications. But I was getting issues with the viewmodel's dictionary being continuously changed while I was accessing it in the MainWindow.

Sorry if this question sounds stupid. I just started with WPF two months ago, and didn't have a great background in programming even before that, either.

Mudslinging answered 7/11, 2013 at 22:19 Comment(2)
Isn't that breaking the whole paradigm of MVVM? The view model shouldn't even know that the view exists. How are you reacting to changes in the Dictionary from the View currently? Could you potentially subscribe to events from the View Model as described in this thread?Fard
Thanks @nekizalb , I'm checking out that thread nowMudslinging
C
32

VM should "know" nothing of your View or Window, the way VM typically "communicates" with V in WPF/MVVM is by rasing events. That way VM remains ignorant of/decoupled from the V and since VM is already DataContext of V it's not hard to subscribe to VM's event.

Example:

VM:

public event EventHandler<NotificationEventArgs<string>> DoSomething;
...
Notify(DoSomething, new NotificationEventArgs<string>("Message"));

V:

var vm = DataContext as SomeViewModel; //Get VM from view's DataContext
if (vm == null) return; //Check if conversion succeeded
vm.DoSomething += DoSomething; // Subscribe to event

private void DoSomething(object sender, NotificationEventArgs<string> e)
{
    // Code    
}
Comprehension answered 7/11, 2013 at 23:9 Comment(10)
Thanks! This seems like exactly what I'm looking for. And it's so simple too..I just have one question: How do I access NotificationEventArgs? I don't have access to it in my project right now. But when I start typing it, the option "NotifyParentPropertyAttribute" comes up. Are those two versions of the same thing? Or am I missing a reference?Mudslinging
That's a part of the MVVM framework I use called Simple MVVM Toolkit. The MVVM implementation is much easier if you use one of the frameworks like the one I mentioned or MVVM Light. You could however write your own version of NotificationEventArgs since the signature is as simple as public class NotificationEventArgs : EventArgs { public NotificationEventArgs(); public NotificationEventArgs(string message); public string Message { get; protected set; } }Comprehension
I also don't have access to Notify :/ @DeanMudslinging
Yes, that's all part of the Simple MVVM Framework. Install that and it will appear. It will also make your MVVM life much easier...Comprehension
Notify is not available from the Simple MVVM Toolkit. Please update the answer with the library it uses please!Paddlefish
@Paddlefish Notify is a method of Simple MVVM Toolkit. It is in ViewModelBaseCore class which is within SimpleMvvmToolkit-Common Assembly. Are you sure you have appropriate references in your project and correct using statements in your code? simplemvvmtoolkit.codeplex.com/SourceControl/latest#Source/…Comprehension
Ah, I see. My ViewModel classes were already implementing IView from System.Waf.Applications, any suggestions for having the answer implemented with this without having to reconstruct the entire class and fix ambiguous references?Paddlefish
Where/what is Notify()?Francinafrancine
I think Notify() is not necessry. I call the following code from VM: DoSomething?.Invoke(this, new NotificationEventArgs<string>("Message"));Sissel
NotificationEventArgs and Notify both not foundImpenetrable
I
14

first of all, it's not a stupid question. Most of MVVM starters came from winforms and it's normal to have the tendency to bring in your winforms practices and work on code behind. Now all you have to do is forget that and think MVVM.

Going back to your question, you have a dictionary that your VM is processing and you are accessing that dictionary from the view. Your view should not have any idea about your viewmodel except through binding.

Making a window flash when there are changes in the viewmodel sounds like an attached behavior to me. Here's a good read about attached behavior. http://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF

To make it easier, I'll try to give you a very simple example that will somehow be relevant to your case.

Create an attached behavior class where you have an IEnumerable where in whenever you add something a messagebox will appear on the screen. Just change the messagebox code to whatever flashing animation you would like to do on notify.

public class FlashNotificationBehavior
{
    public static readonly DependencyProperty FlashNotificationsProperty =
        DependencyProperty.RegisterAttached(
        "FlashNotifications",
        typeof(IEnumerable),
        typeof(FlashNotificationBehavior),
        new UIPropertyMetadata(null, OnFlashNotificationsChange));

    private static void OnFlashNotificationsChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var collection = e.NewValue as INotifyCollectionChanged;

        collection.CollectionChanged += (sender, args) => 
            {
                if (args.Action == NotifyCollectionChangedAction.Add)
                {
                    foreach (var items in args.NewItems)
                        MessageBox.Show(items.ToString());
                }
            };            
    }

    public static IEnumerable GetFlashNotifications(DependencyObject d)
    {
        return (IEnumerable)d.GetValue(FlashNotificationsProperty);
    }

    public static void SetFlashNotifications(DependencyObject d, IEnumerable value)
    {
        d.SetValue(FlashNotificationsProperty, value);
    }
}

In your viewmodel, you can create an ObservableCollection property, you need an observable collection so there is a collection changed event notification. I also added a command for adding so that you can test it.

   public class MainViewModel : ViewModelBase
{
    ObservableCollection<string> notifications;

    public ObservableCollection<string> Notifications
    {
        get { return notifications; }
        set
        {
            if (notifications != value)
            {
                notifications = value;
                base.RaisePropertyChanged(() => this.Notifications);
            }
        }
    }

    public ICommand AddCommand
    {
        get
        {
            return new RelayCommand(() => this.Notifications.Add("Hello World"));
        }
    }

    public MainViewModel()
    {
        this.Notifications = new ObservableCollection<string>();             
    }
}

And here's a view where you can bind it the Notifications proeprty from your view model.

<Window x:Class="WpfApplication7.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vm="clr-namespace:WpfApplication7.ViewModel"
    xmlns:local="clr-namespace:WpfApplication7"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <vm:MainViewModel />
</Window.DataContext>
<Grid>
    <StackPanel>
        <ListBox ItemsSource="{Binding Notifications}" 
                 local:FlashNotificationBehavior.FlashNotifications="{Binding Notifications}"></ListBox>
        <Button Command="{Binding AddCommand}" >Add Something</Button>
    </StackPanel>
</Grid>

Everytime you add something in the ObservableCollection, you will get a messagebox notifying the user that something has been added to your collection.

I hope that I helped in your problem. Just tell me if you need some clarifications.

Irvin answered 8/11, 2013 at 0:47 Comment(1)
Thanks! This explanation helped show me how to set up different parts of me code so that they can interact in a MVVM fashion (which I'm still getting used to)Mudslinging

© 2022 - 2024 — McMap. All rights reserved.