21

I'm trying to develop an easy MVVM project that it has two windows:

  1. The first window is a text editor, where I bind some properties such as FontSize or BackgroundColor:

    <TextBlock FontSize="{Binding EditorFontSize}"></TextBlock>

its DataContext is MainWindowViewModel:

public class MainWindowViewModel : BindableBase
{     
    public int EditorFontSize
    {
        get { return _editorFontSize; }
        set { SetProperty(ref _editorFontSize, value); }
    } 
.....
  1. The second window is the option window, where I have an slider for changing the font size:

<Slider Maximum="30" Minimum="10" Value="{Binding EditorFontSize }" ></Slider>

its DataContext is OptionViewModel:

public class OptionViewModel: BindableBase
{     
    public int EditorFontSize
    {
        get { return _editorFontSize; }
        set { SetProperty(ref _editorFontSize, value); }
    }
.....

My problem is that I have to get the value of the slider in the option window and then I have to modify the FontSize property of my TextBlock with this value. But I don't know how to send the font size from OptionViewModel to MainViewModel.

I think that I should use:

  1. A shared model
  2. A model in MainWindowViewModel and a ref of this model in OptionViewModel
  3. Other systems like notifications, messages ...

I hope that you can help me. It's my first MVVM project and English isn't my main language :S

Thanks

Community
  • 1
  • 1
ganchito55
  • 3,559
  • 4
  • 25
  • 46
  • 1
    I swear--half the questions in MVVM is about this situation-two windows, how do view models for each communicate. It's pretty trivial, I'm not sure why people have issues with it. But then, I'm dumb. This question is good and clear, and the answers are good. I'm going to use this as a canonical and dupehammer these questions for now on. –  Feb 11 '16 at 16:52
  • 6
    Maybe because there are more concepts involved into writing an MVVM app, than just MVVM itself. Dependency Injection, nth-layer design, and concept of services and repositories. Beginners just look at MVVM as XAML (View), Model (data structure) and ViewModel (everything else), not realizing VM and M are just layers, not objects. Model has more than just data structures, it has repositories (at least interfaces for it), services, business logic. One someone realize that, all the other solutions (event aggregator as just another service, etc.) become really obvious – Tseng Feb 11 '16 at 16:59
  • 3
    @user1228 suggests that this is trivial and yet the accepted answer is to use a framework. If one has to use a framework then it is not trivial. – Paul McCarthy Jul 28 '21 at 10:29

6 Answers6

16

Another option is to store such "shared" variables in a SessionContext-class of some kind:

public interface ISessionContext: INotifyPropertyChanged 
{
    int EditorFontSize { get;set; }
}

Then, inject this into your viewmodels (you are using Dependency Injection, right?) and register to the PropertyChanged event:

public class MainWindowViewModel 
{
    public MainWindowViewModel(ISessionContext sessionContext)
    {
        sessionContext.PropertyChanged += OnSessionContextPropertyChanged;        
    }

    private void OnSessionContextPropertyChanged(object sender, PropertyChangedEventArgs e) 
    {
        if (e.PropertyName == "EditorFontSize")
        {
            this.EditorFontSize = sessionContext.EditorFontSize;
        }
    }       
}
RoelF
  • 7,483
  • 5
  • 44
  • 67
11

There are many ways to communicate between view models and a lot of points what the point is the best. You can see how it is done:

In my view, the best approach is using EventAggregator pattern of Prism framework. The Prism simplifies MVVM pattern. However, if you have not used Prism, you can use Rachel Lim's tutorial - simplified version of EventAggregator pattern by Rachel Lim.. I highly recommend you Rachel Lim's approach.

If you use Rachel Lim's tutorial, then you should create a common class:

public static class EventSystem
{...Here Publish and Subscribe methods to event...}

And publish an event into your OptionViewModel:

eventAggregator.GetEvent<ChangeStockEvent>().Publish(
new TickerSymbolSelectedMessage{ StockSymbol = “STOCK0” });

then you subscribe in constructor of another your MainViewModel to an event:

eventAggregator.GetEvent<ChangeStockEvent>().Subscribe(ShowNews);

public void ShowNews(TickerSymbolSelectedMessage msg)
{
   // Handle Event
}

The Rachel Lim's simplified approach is the best approach that I've ever seen. However, if you want to create a big application, then you should read this article by Magnus Montin and at CSharpcorner with an example.

Update: For versions of Prism later than 5 CompositePresentationEvent is depreciated and completely removed in version 6, so you will need to change it to PubSubEvent everything else can stay the same.

StepUp
  • 36,391
  • 15
  • 88
  • 148
  • 4
    Was about to write a similar answer, but Event aggregator pattern/message bus is the way to go. However, I disagree with the event system being used as static as this is an anti-pattern and makes testability and switching components way more difficult. event aggregator should (like all other services and dependencies be injected into the ViewModel (preferably using IoC Container, though not mandatory) – Tseng Feb 11 '16 at 16:49
  • @Tseng I highly agree with you, however it makes to learn Prism by OP. In my view, if we have simple application, then it is easier to use Rachel Lim's approach. – StepUp Feb 11 '16 at 16:52
  • 1
    Dependency injection is not a feature of Prism and Prism itself is no IoC container. event aggregator is just a name, it can also be a custom implementation that is injected via DI/IoC though (as long as it's "static" within the containers life time, usually the lifetime of the application. Just w/o the static keyword :) – Tseng Feb 11 '16 at 16:55
  • @Tseng yep. I am a follower of Prism cause Prism really gives a real separation of concerns. We have various, separate "bricks(modules)" to build "house(application)". And each brick can be created by various teams without interruption other teams. I really like Prism!:) – StepUp Feb 11 '16 at 19:10
  • @Tseng I do not know whether it is true or not that Microsoft uses Prism to write Visual Studio :) – StepUp Feb 12 '16 at 10:54
  • 1
    Rachel Lims code is slightly outdated, you need to swap `CompositePresentationEvent` for `PubSubEvent` for Prism version > 6 –  Jun 11 '18 at 10:24
  • @MattBeldon feel free to add your comment to answer – StepUp Jun 11 '18 at 16:34
4

I have done a big MVVM application with WPF. I have a lot of windows and I had the same problem. My solution maybe isn't very elegant, but it works perfectly.

First solution: I have done one unique ViewModel, splitting it in various file using a partial class.

All these files start with:

namespace MyVMNameSpace
{
    public partial class MainWindowViewModel : DevExpress.Mvvm.ViewModelBase
    {
        ...
    }
}

I'm using DevExpress, but, looking your code you have to try:

namespace MyVMNameSpace
{
    public partial class MainWindowViewModel : BindableBase
    {
        ...
    }
}

Second solution: Anyway, I have also a couple of different ViewModel to manage some of these windows. In this case, if I have some variables to read from one ViewModel to another, I set these variables as static.

Example:

    public static event EventHandler ListCOMChanged;
    private static List<string> p_ListCOM;
    public static List<string> ListCOM
    {
        get { return p_ListCOM; }
        set 
        {
            p_ListCOM = value;
            if (ListCOMChanged != null)
                ListCOMChanged(null, EventArgs.Empty);
        }
    }

Maybe the second solution is simplier and still ok for your need.

I hope this is clear. Ask me more details, if you want.

MattC
  • 3,984
  • 1
  • 33
  • 49
Piero Alberto
  • 3,823
  • 6
  • 56
  • 108
2

I'm not a MVVM pro myself, but what I've worked around with problems like this is, having a main class that has all other view models as properties, and setting this class as data context of all the windows, I don't know if its good or bad but for your case it seems enough.

For a more sophisticated solution see this

For the simpler one,

You can do something like this,

public class MainViewModel : BindableBase
{
    FirstViewModel firstViewModel;

    public FirstViewModel FirstViewModel
    {
        get
        {
            return firstViewModel;
        }

        set
        {
            firstViewModel = value;
        }
    }

    public SecondViewModel SecondViewModel
    {
        get
        {
            return secondViewModel;
        }
        set
        {
            secondViewModel = value;
        }
    }

    SecondViewModel secondViewModel;

    public MainViewModel()
    {
        firstViewModel = new FirstViewModel();
        secondViewModel = new SecondViewModel();
    }
}

now you have to make another constructor for your OptionWindow passing a view model.

 public SecondWindow(BindableBase viewModel)
    {
        InitializeComponent();
        this.DataContext = viewModel;
    }

this is to make sure that both windows work on the same instance of a view model.

Now, just wherever you're opening the second window use these two lines

var window = new SecondWindow((ViewModelBase)this.DataContext);
        window.Show();

now you're passing the First Window's view model to the Second window, so that they work on the same instance of the MainViewModel.

Everything is done, just you've to address to binding as

<TextBlock FontSize="{Binding FirstViewModel.EditorFontSize}"></TextBlock>
<TextBlock FontSize="{Binding SecondViewModel.EditorFontSize}"></TextBlock>

and no need to say that the data context of First window is MainViewModel

Manish Singh
  • 360
  • 1
  • 6
  • 18
0

In MVVM, models are the shared data store. I would persist the font size in the OptionsModel, which implements INotifyPropertyChanged. Any viewmodel interested in font size subscribes to PropertyChanged.

class OptionsModel : BindableBase
{
    public int FontSize {get; set;} // Assuming that BindableBase makes this setter invokes NotifyPropertyChanged
}

In the ViewModels that need to be updated when FontSize changes:

internal void Initialize(OptionsModel model)
{
    this.model = model;
    model.PropertyChanged += ModelPropertyChanged;

    // Initialize properties with data from the model
}

private void ModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    if (e.PropertyName == nameof(OptionsModel.FontSize))
    {
        // Update properties with data from the model
    }
}
Amadeusz Wieczorek
  • 3,139
  • 2
  • 28
  • 32
0

I'm new to WPF and I've come up with a solution to this and I'm curious of more knowledgeable people's thoughts about what's right and wrong with it.

I have an Exams tab and a Templates tab. In my simple proof of concept, I want each tab to "own" an Exam object, and to be able to access the other tab's Exam.

I define the ViewModel for each tab as static because if it's a normal instance property, I don't know how one tab would get the actual instance of the other tab. It feels wrong to me, though it's working.

namespace Gui.Tabs.ExamsTab {
    public class GuiExam: INotifyPropertyChanged {
        private string _name = "Default exam name";
        public string Name { 
            get => _name;
            set {
                _name = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

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

    public partial class ExamsHome : Page {
        public ExamsHome() {
            InitializeComponent();
            DataContext = ViewModel;
        }

        public static readonly ExamsTabViewModel ViewModel = new ExamsTabViewModel();
    }

    public class ExamsTabViewModel { 
        public GuiExam ExamsTabExam { get; set; } = new GuiExam() { Name = "Exam from Exams Tab" };
        public GuiExam FromTemplatesTab { get => TemplatesHome.ViewModel.TemplatesTabExam; }
    }
}

namespace Gui.Tabs.TemplatesTab {
    public partial class TemplatesHome : Page {
        public TemplatesHome() {
            InitializeComponent();
            DataContext = ViewModel;
        }

        public static readonly TemplatesTabViewModel ViewModel = new TemplatesTabViewModel();
    }

    public class TemplatesTabViewModel {
        public GuiExam TemplatesTabExam { get; set; } = new GuiExam() { Name = "Exam from Templates Tab" };
        public GuiExam FromExamTab { get => ExamsHome.ViewModel.ExamsTabExam; }
    }
}

And then everything is accessible in the xaml:

TemplatesHome.xaml (excerpt)

<StackPanel Grid.Row="0">
    <Label Content="From Exams Tab:"/>
    <Label FontWeight="Bold" Content="{Binding FromExamTab.Name}"/>
</StackPanel>

<StackPanel Grid.Row="1">
    <Label Content="Local Content:"/>
    <TextBox Text="{Binding TemplatesTabExam.Name, UpdateSourceTrigger=PropertyChanged}"
    HorizontalAlignment="Center" Width="200" FontSize="16"/>
</StackPanel>

ExamsHome.xaml (excerpt)

<StackPanel Grid.Row="0">
    <Label Content="Local Content:"/>
    <TextBox Text="{Binding ExamsTabExam.Name, UpdateSourceTrigger=PropertyChanged}"
    HorizontalAlignment="Center" Width="200" FontSize="16"/>
</StackPanel>

<StackPanel Grid.Row="1">
    <Label Content="From Templates Tab:"/>
    <Label FontWeight="Bold" Content="{Binding FromTemplatesTab.Name}"/>
</StackPanel>
Jonathan Tuzman
  • 11,568
  • 18
  • 69
  • 129