Reputation: 137
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
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.
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:
Constructor parameter:
var window1 = new Window1("hello world");
windows1.Show();
public class Window1(string parameter){...
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.
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
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
I'll really glad to help with additional explanations if needed. Just let me know.
Regards.
Upvotes: 0
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