wpf MVVM C#​​ 在视图之间传递数据(窗口)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/37046584/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 13:47:35  来源:igfitidea点击:

MVVM C# Pass data between Views(window)

c#wpfmvvm

提问by shiv

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

1)在多个视图之间传递数据的最佳方法是什么?

2) I have scenario(MVVM C#):

2)我有场景(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 ?

MainWindow 中的 TextBox 和 Button 以及 Window1 中的 TextBlock,在单击按钮时(我正在使用 Icommand),MainWindow 的 TextBox 中的数据必须出现在 Window1 的 TextBlock 中?

ViewModelBase.cs

视图模型库.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

命令类.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.

我用谷歌搜索并找到了一个项目,但它很复杂,请建议我的 2) 问题的答案会有所帮助..谢谢。

回答by Pikoh

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:

看到您的代码,您可以这样做,因为我认为两个窗口都共享 ViewModelBase:

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

You also have to bind your TextBlock:

您还必须绑定您的 TextBlock:

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

回答by Liero

I assume that you intended to share ViewModelBasebetween 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.

我假设您打算ViewModelBase在 MainWindow 和 Window1 之间共享。在这种情况下,您不能将 ViewModelBase 实例添加到 MainWindow.Resources,然后将另一个实例添加到 Window1.Resources。如果您不明白为什么,请查看一些 C# OOP 教程。

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

要在多个视图之间共享同一个 ViewModelBase 实例,您必须只创建一个资源。Application.Resources 可从所有视图访问。

  1. Add ViewModelBase to Application.Resources in app.xaml
  2. Remove ViewModelBase from MainWindow.Resources and Window1.Resources
  1. 将 ViewModelBase 添加到 app.xaml 中的 Application.Resources
  2. 从 MainWindow.Resources 和 Window1.Resources 中删除 ViewModelBase

that's it.

就是这样。



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

但是,建议您为每个视图使用单独的 ViewModel 类。

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:

除了@Pikoh 的解决方案,我还提出以下建议:

  1. Constructor parameter:

    var window1 = new Window1("hello world");
    windows1.Show();
    
    public class Window1(string parameter){...
    
  2. I preffer ViewModel property on the Viewand 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 firstapproach:

    a) create viewmodel for second window

    b) set the parameters on the viewmodel

    c) use custom DialogServiceclass 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); 
    
  1. 构造函数参数

    var window1 = new Window1("hello world");
    windows1.Show();
    
    public class Window1(string parameter){...
    
  2. 我喜欢在视图上使用 ViewModel 属性,并让在视图上创建 ViemModel 的责任。

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

    数据上下文应该在 ctor 或 XAML 中设置。

  3. ViewModel 第一种方法:

    a) 为第二个窗口创建视图模型

    b) 在视图模型上设置参数

    c) 使用自定义DialogService类根据视图模型类型使用命名约定为您创建视图并显示它。

    通过这种方式,您甚至不会触及视图模型中的任何视图,并且您的视图模型与视图完全分离,因此很容易测试。您可以在运行单元测试时轻松替换 DialogService 实现。.

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

回答by vercin

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

如果要在视图模型之间共享数据,则需要将中介器实现应用于代码。

please have a look hereyou will find further information about it.

请看这里,您将找到有关它的更多信息。

回答by Ilan

Here is full answer for your question:

以下是您问题的完整答案:

Main window xaml code

主窗口xaml代码

<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

第二个窗口 xaml 代码

<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

第二个窗口加载行为,有助于控制 SecondWindow 外观,并将 ICanProvideDataForSecondWindow 注入到第二个窗口视图模型的 c'tor 中

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

主窗口视图模型,实现 ICanProvideDataForSecondWindow 以便能够即时发送数据和更改第二个窗口内容

 /// <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

ICanProvideDataForSecondWindow - 帮助主窗口和第二窗口视图模型之间通信的接口

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

DataForSecondWindowArgs - argument delivered during the view model communication

DataForSecondWindowArgs - 在视图模型通信期间传递的参数

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

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

SecondWindow 数据上下文 - 是侦听器,通过 ICanProvideDataForSecondWindow 接口获取数据。

 /// <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

INPC 实现和命令(你可以用你自己的命令类替换它)代码

 /// <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 injectionof 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).
  1. MainWindow - 就像你的第一个窗口。
  2. SecondWindow - 就像你的第二个窗口。
  3. SecondWindowLoadingBehavior - 行为(链接 -什么是行为以及如何创建行为行为 dll 在哪里)用于控制第二个窗口的外观。
  4. FirstWindowDataContext - 主窗口数据上下文,是第二个窗口的数据提供者。
  5. ICanProvideDataForSecondWindow - 为第二个窗口提供数据的接口(适用于SOLID依赖注入)。
  6. SecondWindowDataContext - 第二个窗口数据上下文,监听主窗口数据上下文中的变化(这里 ICanProvideDataForSecondWindow 被注入到它的 c'tor)。
  7. 此外,您还可以看到一些基于 IEventAggregator 的解决方案(EventAggregator 示例)。

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

如果需要,我真的很乐意帮助进行额外的解释。只要让我知道。

Regards.

问候。