shiv
shiv

Reputation: 137

MVVM C# Pass data between Views(window)

1) What are the best ways to pass data between multiple Views?

2) I have scenario(MVVM C#):

TextBox and Button in MainWindow and TextBlock in Window1, on Button click(I am using Icommand) the Data in TextBox of MainWindow has to appear in TextBlock of Window1 ?

ViewModelBase.cs

public class ViewModelBase
{
    public Commandclass commandclass { get; set; }

    public ViewModelBase()
    {
        commandclass = new Commandclass(this);
    }

    private string fname;        
    public string vmname
    {
        get { return fname; }
        set { fname = value; }
    }        

    public void OnCommand()
    {         
        Window1 w = new Window1();
        /* How to bind ???*/
        w.Show();
    }
}

CommandClass.cs

public class Commandclass : ICommand
    {
        public ViewModelBase vmclass { get; set; }
        public Commandclass(ViewModelBase vmb)
        {
            vmclass = vmb;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }
        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            vmclass.OnCommand();
        }
    }

Views

**MainWindow.xaml**

<Window x:Class="Multiwindow.MainWindow"
        …
        xmlns:vm="clr-namespace:Multiwindow.Viewmodel">
    <Window.Resources>        
        <vm:ViewModelBase x:Key="vmodel"/>
    </Window.Resources>

    <Grid Background="Gray" DataContext="{StaticResource vmodel}">

        <TextBox Height="26" Margin="194,115,154,179" Width="169" 
                  Text="{Binding vmname, Mode=TwoWay}"/>
        <Button Content="Button1" HorizontalAlignment="Left" 
                Margin="251,158,0,0" VerticalAlignment="Top"
                Command="{Binding commandclass, Source={StaticResource vmodel}}"/>

    </Grid>
</Window>

**Window1.xaml**

<Window.Resources>
        <vm:ViewModelBase x:Key="vmodel"/>
</Window.Resources>
<Grid >
    <TextBlock FontSize="20" Height="28" Width="169" Foreground="Black"
                    Background="Bisque" />
</Grid>

I have googled and found one project but it is to complex, Please suggest an answer to my 2) question will be helpfull.. Thanks.

Upvotes: 6

Views: 18726

Answers (4)

Liero
Liero

Reputation: 27328

I assume that you intended to share ViewModelBase between MainWindow and Window1. In that case you can't add ViewModelBase instance to MainWindow.Resources and then another instance to Window1.Resources. If you don't unserstand why, please check some C# OOP tutorial.

To share the same instance of ViewModelBase between multiple view you must create only one resource. Application.Resources are accessible from all view.

  1. Add ViewModelBase to Application.Resources in app.xaml
  2. Remove ViewModelBase from MainWindow.Resources and Window1.Resources

that's it.


However, it is recommended, that you have separate ViewModel classes for each View.

In that case you could have somthing like this:

<Window x:Class="Multiwindow.MainWindow"
        xmlns:vm="clr-namespace:Multiwindow.Viewmodel">
    <Window.DataContext>        
        <vm:MainWindowViewModel />
    </Window.DataContext>
    ...
</Window>

**Window1.xaml**

<Window.DataContext>
    <vm:Window1ViewModel />
</Window.Resources>

In addition to @Pikoh's solution I propose following:

  1. Constructor parameter:

    var window1 = new Window1("hello world");
    windows1.Show();
    
    public class Window1(string parameter){...
    
  2. I preffer ViewModel property on the View and let the reposibility for creating ViemModel on the View.

    var window1 = new Window1();
    window1.ViewModel.Parameter = "Hello world";
    
    public class Window1{
       ...
       public Window1ViewModel { get {return (Window1ViewModel)DataContext;}}
    }
    

    datacontext should be set in ctor or in XAML.

  3. ViewModel first approach:

    a) create viewmodel for second window

    b) set the parameters on the viewmodel

    c) use custom DialogService class to create the view for you based on the viewmodel type using naming convention and show it.

    this way you dont even touch any view in your viewmodels and you have your viewmodel trully separated from your views, so its easily testable. You can easily replace DialogService implementation when running unit tests. .

    //in MainWindowViewModel:
    
    var secondWindowViewModel = new SecondWindowViewModel();
    //alternativelly:
    //secondWindowViewModel = ViewModelLocator.Resolve<SecondWindowViewModel>();
    secondWindowViewModel.Parameter = "Hello world";
    dialogService.Show(secondWindowViewModel); 
    

Upvotes: 2

Ilan
Ilan

Reputation: 2772

Here is full answer for your question:

Main window xaml code

<Window x:Class="TwoWindowsDialogSoHelpAttempt.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:twoWindowsDialogSoHelpAttempt="clr-namespace:TwoWindowsDialogSoHelpAttempt"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <!--Main window data context declaration-->
    <twoWindowsDialogSoHelpAttempt:FirstWindowDataContext/>
</Window.DataContext>
<i:Interaction.Behaviors>
    <!--next behavior helps to control loading of the second window-->
    <twoWindowsDialogSoHelpAttempt:SecondWindowLoadingBehavior 
        IsSecondWindowShouldBeShown="{Binding IsSecondWindowShouldBeShown, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
        CanProvideDataForSecondWindow="{Binding CanProvideDataForSecondWindow, UpdateSourceTrigger=PropertyChanged}"/>
</i:Interaction.Behaviors>
<Grid>
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <Border Width="150" Height="30" BorderBrush="Tomato" BorderThickness="1">
            <TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"></TextBox>
        </Border>
        <Button Command="{Binding Command, UpdateSourceTrigger=PropertyChanged}">Show second Window</Button>
    </StackPanel>
</Grid>

Second window xaml code

<Window x:Class="TwoWindowsDialogSoHelpAttempt.SecondWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="SecondWindow" Height="300" Width="300">
<Grid Background="Green">
    <TextBlock Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="150"></TextBlock>
</Grid>

Second window loading behavior, helps to control the SecondWindow appearance, and injects the ICanProvideDataForSecondWindow into the c'tor og the second window view model

public class SecondWindowLoadingBehavior:Behavior<FrameworkElement>
{
    private Window _window;


    public static readonly DependencyProperty CanProvideDataForSecondWindowProperty = DependencyProperty.Register(
        "CanProvideDataForSecondWindow", typeof (ICanProvideDataForSecondWindow), typeof (SecondWindowLoadingBehavior), new PropertyMetadata(default(ICanProvideDataForSecondWindow)));

    /// <summary>
    /// helps to control dialog between first and second window
    /// </summary>
    public ICanProvideDataForSecondWindow CanProvideDataForSecondWindow
    {
        get { return (ICanProvideDataForSecondWindow) GetValue(CanProvideDataForSecondWindowProperty); }
        set { SetValue(CanProvideDataForSecondWindowProperty, value); }
    }

    public static readonly DependencyProperty IsSecondWindowShouldBeShownProperty = DependencyProperty.Register(
        "IsSecondWindowShouldBeShown", typeof (bool), typeof (SecondWindowLoadingBehavior), new PropertyMetadata(default(bool), IsSecondWindowShouldBeShownPropertyChangedCallback));

    //when the IsSecondWindowShouldBeShown dependency property will be changed, will trigger the window showing
    private static void IsSecondWindowShouldBeShownPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
    {
        var isShouldBeShown = (bool) args.NewValue;
        var behavior = dependencyObject as SecondWindowLoadingBehavior;
        if (isShouldBeShown == false || behavior == null || behavior.CanProvideDataForSecondWindow == null)
        {
            return;
        }
        behavior.ShowSecondWindow();
    }

    /// <summary>
    /// helps to control the second window loading
    /// </summary>
    public bool IsSecondWindowShouldBeShown
    {
        get { return (bool)GetValue(IsSecondWindowShouldBeShownProperty); }
        set { SetValue(IsSecondWindowShouldBeShownProperty, value); }
    }

    //helps to prepare and show the second window
    private void ShowSecondWindow()
    {
        _window = new SecondWindow {DataContext = new SecondWindowDataContext(CanProvideDataForSecondWindow)};
        _window.Closing += WindowOnClosing;
        _window.Show();
    }

    //disposes a data context instance when a window was closed
    private void WindowOnClosing(object sender, CancelEventArgs e)
    {
        _window.Closing -= WindowOnClosing;
        IsSecondWindowShouldBeShown = false;
        var disposableDataContext = _window.DataContext as IDisposable;
        if (disposableDataContext == null) return;
        disposableDataContext.Dispose();
    }
}

View models and helpers

Main window view model, implements the ICanProvideDataForSecondWindow to be able to send data and change the second window content on the fly

 /// <summary>
/// a first window data context, provides data to the second window based on a
///  ICanProvideDataForSecondWindow implementation
/// </summary>
public class FirstWindowDataContext:BaseObservableObject, ICanProvideDataForSecondWindow
{
    private string _text;
    private ICommand _command;
    private bool _isSecondWindowShouldBeShown;
    private ICanProvideDataForSecondWindow _canProvideDataForSecondWindow;

    public FirstWindowDataContext()
    {
        CanProvideDataForSecondWindow = this;
    }

    public ICanProvideDataForSecondWindow CanProvideDataForSecondWindow
    {
        get { return _canProvideDataForSecondWindow; }
        set
        {
            _canProvideDataForSecondWindow = value;
            OnPropertyChanged();
        }
    }

    public string Text
    {
        get { return _text; }
        set
        {
            _text = value;
            OnPropertyChanged();
            OnSecondWindowDataEventHandler(new DataForSecondWindowArgs{Data = Text});
        }
    }

    public ICommand Command
    {
        get { return _command ?? (_command = new RelayCommand(ShowSecondWindow)); }
    }

    //method to show the second window
    private void ShowSecondWindow()
    {
        //will show the window if it isn't opened yet
        if(IsSecondWindowShouldBeShown) return;
        IsSecondWindowShouldBeShown = true;
    }

    public bool IsSecondWindowShouldBeShown
    {
        get { return _isSecondWindowShouldBeShown; }
        set
        {
            _isSecondWindowShouldBeShown = value;
            OnPropertyChanged();
        }
    }

    public event EventHandler<DataForSecondWindowArgs> SecondWindowDataEventHandler;

    protected virtual void OnSecondWindowDataEventHandler(DataForSecondWindowArgs e)
    {
        var handler = SecondWindowDataEventHandler;
        if (handler != null) handler(this, e);
    }
}

ICanProvideDataForSecondWindow - interface to help to communicate between main and second windows view models

public interface ICanProvideDataForSecondWindow
{
    event EventHandler<DataForSecondWindowArgs> SecondWindowDataEventHandler;
    string Text { get; set; }
}

DataForSecondWindowArgs - argument delivered during the view model communication

public class DataForSecondWindowArgs:EventArgs
{
    public string Data { get; set; }
}

The SecondWindow data context - is the listener, get data by the ICanProvideDataForSecondWindow interface.

 /// <summary>
/// second window data context, listening for the changes in first window data context to show them on the second window
/// </summary>
class SecondWindowDataContext:DisposableBaseObservableObject
{
    private readonly ICanProvideDataForSecondWindow _canProvideDataForSecondWindow;
    private string _text;

    public SecondWindowDataContext(ICanProvideDataForSecondWindow canProvideDataForSecondWindow)
    {
        _canProvideDataForSecondWindow = canProvideDataForSecondWindow;
        _canProvideDataForSecondWindow.SecondWindowDataEventHandler += CanProvideDataForSecondWindowOnSecondWindowDataEventHandler;
        Text = _canProvideDataForSecondWindow.Text;
    }

    private void CanProvideDataForSecondWindowOnSecondWindowDataEventHandler(object sender, DataForSecondWindowArgs args)
    {
        Text = args.Data;
    }

    public string Text
    {
        get { return _text; }
        set
        {
            _text = value;
            OnPropertyChanged();
        }
    }

    protected override void DisposeOverride()
    {
        base.DisposeOverride();
        _canProvideDataForSecondWindow.SecondWindowDataEventHandler -= CanProvideDataForSecondWindowOnSecondWindowDataEventHandler;
    }
}

INPC implementation and command(you can replace this with your own Commandclass) code

 /// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class DisposableBaseObservableObject : INotifyPropertyChanged, IDisposable
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
    {
        var propName = ((MemberExpression)raiser.Body).Member.Name;
        OnPropertyChanged(propName);
    }

    protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
    {
        if (!EqualityComparer<T>.Default.Equals(field, value))
        {
            field = value;
            OnPropertyChanged(name);
            return true;
        }
        return false;
    }

    #region Disposable

    private bool _isDisposed;
    ~DisposableBaseObservableObject()
    {
        Dispose();
    }

    public void Dispose()
    {
        if (_isDisposed)
        {
            return;
        }
        GC.SuppressFinalize(this);
        _isDisposed = true;
        DisposeOverride();
    }

    protected virtual void DisposeOverride()
    {

    }

    #endregion
}

public class RelayCommand : ICommand
{
    private readonly Func<bool> _canExecute;
    private readonly Action _execute;

    public RelayCommand(Action execute)
        : this(() => true, execute)
    {
    }

    public RelayCommand(Func<bool> canExecute, Action execute)
    {
        _canExecute = canExecute;
        _execute = execute;
    }

    public bool CanExecute(object parameter = null)
    {
        return _canExecute();
    }

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

    public event EventHandler CanExecuteChanged;
}

Explanations

  1. MainWindow - like your first window.
  2. SecondWindow - like your second window.
  3. SecondWindowLoadingBehavior - behavior(links - what is behavior and how to create behavior, where is behavior dll) use to control the second window appearance.
  4. FirstWindowDataContext - the main window data context, is a data provider for the second window.
  5. ICanProvideDataForSecondWindow - interface to provide data for the second window(good for a dependency injection of SOLID).
  6. SecondWindowDataContext - the second window data context, listens for the changes in the main window data context(here the ICanProvideDataForSecondWindow is injected to it's c'tor).
  7. In addition you can see some IEventAggregator based solutions(EventAggregator example).

I'll really glad to help with additional explanations if needed. Just let me know.

Regards.

Upvotes: 0

Pikoh
Pikoh

Reputation: 7703

This is how i would do this. In the command called on button click I'd do this:

Window2 w= new Window2();
w.DataContext=new Window2ViewModel();
((Window2ViewModel)w.DataContext).TextForTextblock=TextFromTextbox;
w.Show();

Edit

Seeing your code, You can do this as I think both windows share ViewModelBase:

Window1 w= new Window1();
w.DataContext=this;
w.Show();

You also have to bind your TextBlock:

<TextBlock FontSize="20" Height="28" Width="169" Foreground="Black"
                Background="Bisque" Text="{Binding vmname}"/>

Upvotes: 5

vercin
vercin

Reputation: 220

If you want to share data between viewmodels, you need to apply mediator implementation to your code.

please have a look here you will find further information about it.

Upvotes: 0

Related Questions