wpf 在不同的 ViewModel 之间共享数据

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35344436/
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:40:30  来源:igfitidea点击:

Sharing data between different ViewModels

c#wpfmvvm

提问by ganchito55

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

我正在尝试开发一个简单的 MVVM 项目,它有两个窗口:

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

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

  1. 第一个窗口是一个文本编辑器,我在其中绑定了一些属性,例如FontSizeBackgroundColor

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

its DataContextis MainWindowViewModel:

DataContextMainWindowViewModel

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:
  1. 第二个窗口是选项窗口,我有一个用于更改字体大小的滑块:

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

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

its DataContextis OptionViewModel:

DataContextOptionViewModel

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

My problemis 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.

我的问题是我必须在选项窗口中获取滑块的值,然后我必须用这个值修改我的 TextBlock 的 FontSize 属性。但我不知道如何将字体大小从 OptionViewModel 发送到 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 ...
  1. 共享模式
  2. MainWindowViewModel 中的模型和 OptionViewModel 中该模型的参考
  3. 其他系统,如通知、消息......

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

我希望你能帮助我。这是我的第一个 MVVM 项目,英语不是我的主要语言 :S

Thanks

谢谢

采纳答案by StepUp

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 EventAggregatorpattern of Prismframework. 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.

在我看来,最好的方法是使用框架EventAggregator模式Prism。Prism 简化了 MVVM 模式。 但是,如果您还没有使用过Prism,您可以使用Rachel Lim 的教程 - Rachel Lim 的 EventAggregator 模式的简化版本。. 我强烈推荐你 Rachel Lim 的方法。

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

如果您使用 Rachel Lim 的教程,那么您应该创建一个通用类:

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

And publish an event into your OptionViewModel:

并将事件发布到您的OptionViewModel

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

then you subscribe in constructorof another your MainViewModelto an event:

然后你在另一个你的构造函数中订阅MainViewModel一个事件:

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 Montinand at CSharpcorner with an example.

Rachel Lim 的简化方法是我见过的最好的方法。但是,如果您想创建一个大型应用程序,那么您应该阅读Magnus MontinCSharpcorner 的这篇文章示例

Update: For versions of Prismlater than 5 CompositePresentationEventis depreciated and completely removed in version 6, so you will need to change it to PubSubEventeverything else can stay the same.

更新:对于Prism5 以后的版本CompositePresentationEvent已在版本 6 中折旧并完全删除,因此您需要将其更改为PubSubEvent其他所有内容都可以保持不变。

回答by RoelF

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

另一种选择是将此类“共享”变量存储在某种SessionContext-class 中:

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

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

然后,将其注入您的视图模型(您正在使用依赖注入,对吗?)并注册到PropertyChanged事件:

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;
        }
    }       
}

回答by Piero Alberto

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.

我用 WPF 做了一个很大的 MVVM 应用程序。我有很多窗户,我遇到了同样的问题。我的解决方案可能不是很优雅,但效果很好。

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

第一个解决方案:我做了一个独特的 ViewModel,使用部分类将它拆分到各种文件中。

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:

我正在使用 DevExpress,但是,查看您的代码,您必须尝试:

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.

第二种解决方案:无论如何,我还有几个不同的 ViewModel 来管理其中一些窗口。在这种情况下,如果我有一些变量要从一个 ViewModel 读取到另一个,我将这些变量设置为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.

我希望这很清楚。如果你愿意,问我更多细节。

回答by Manish Singh

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.

我自己不是 MVVM 专业人士,但我在处理此类问题时遇到的问题是,拥有一个将所有其他视图模型作为属性的主类,并将此类设置为所有窗口的数据上下文,我不这样做不知道它是好是坏,但对于您的情况来说似乎已经足够了。

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 OptionWindowpassing a view model.

现在您必须为传递视图模型的OptionWindow创建另一个构造函数。

 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.

现在您将第一个窗口的视图模型传递给第二个窗口,以便它们在 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

而且不用说First window的data context是MainViewModel

回答by Jonathan Tuzman

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.

我是 WPF 的新手,我想出了一个解决方案,我很好奇更多知识渊博的人对它的正确与错误的想法。

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

我有一个考试选项卡和一个模板选项卡。在我的简单概念证明中,我希望每个选项卡“拥有”一个Exam对象,并且能够访问另一个选项卡的Exam.

I define the ViewModel for each tab as staticbecause 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.

我为每个选项卡定义了 ViewModel,static因为如果它是一个普通的实例属性,我不知道一个选项卡将如何获得另一个选项卡的实际实例。我感觉不对,尽管它正在起作用。

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:

然后所有内容都可以在 xaml 中访问:

TemplatesHome.xaml(excerpt)

TemplatesHome.xaml(摘抄)

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

ExamsHome.xaml(摘抄)

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

回答by Amadeusz Wieczorek

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.

在 MVVM 中,模型是共享数据存储。我会坚持的字体大小OptionsModel,它实现INotifyPropertyChanged。任何对字体大小感兴趣的视图模型都会订阅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:

在FontSize改变时需要更新的ViewModels中:

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