簡體   English   中英

在不同的 ViewModel 之間共享數據

[英]Sharing data between different ViewModels

我正在嘗試開發一個簡單的 MVVM 項目,它有兩個窗口:

  1. 第一個窗口是一個文本編輯器,我在其中綁定了一些屬性,例如FontSizeBackgroundColor

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

它的DataContextMainWindowViewModel

public class MainWindowViewModel : BindableBase
{     
    public int EditorFontSize
    {
        get { return _editorFontSize; }
        set { SetProperty(ref _editorFontSize, value); }
    } 
.....
  1. 第二個窗口是選項窗口,我有一個用於更改字體大小的滑塊:

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

它的DataContextOptionViewModel

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

我的問題是我必須在選項窗口中獲取滑塊的值,然后我必須用這個值修改我的 TextBlock 的 FontSize 屬性。 我不知道如何將字體大小從 OptionViewModel 發送到 MainViewModel

我認為我應該使用:

  1. 共享模型
  2. MainWindowViewModel 中的模型和 OptionViewModel 中該模型的參考
  3. 其他系統,如通知、消息......

我希望你能幫助我。 這是我的第一個 MVVM 項目,英語不是我的主要語言 :S

謝謝

另一種選擇是將此類“共享”變量存儲在某種SessionContext類中:

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

然后,將其注入您的視圖模型(您正在使用依賴注入,對嗎?)並注冊到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;
        }
    }       
}

有很多方法可以在視圖模型和很多點之間進行交流,什么點是最好的。 你可以看到它是如何完成的:

在我看來,最好的方法是使用Prism框架的EventAggregator模式。 Prism 簡化了 MVVM 模式。 但是,如果您還沒有使用Prism ,您可以使用Rachel Lim 的教程 - Rachel Lim 的 EventAggregator 模式的簡化版本。 . 我強烈推薦你 Rachel Lim 的方法。

如果您使用 Rachel Lim 的教程,那么您應該創建一個通用類:

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

並將事件發布到您的OptionViewModel

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

然后你在另一個MainViewModel構造函數中訂閱一個事件:

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

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

Rachel Lim 的簡化方法是我見過的最好的方法。 但是,如果您想創建一個大型應用程序,那么您應該閱讀Magnus MontinCSharpcorner 的這篇文章並附有示例

更新:對於 5 之后的Prism版本, CompositePresentationEvent在版本 6 中已棄用並完全刪除,因此您需要將其更改為PubSubEvent其他一切都可以保持不變。

我用 WPF 做了一個很大的 MVVM 應用程序。 我有很多窗戶,我遇到了同樣的問題。 我的解決方案可能不是很優雅,但效果很好。

第一個解決方案:我做了一個獨特的 ViewModel,使用部分類將它拆分到各種文件中。

所有這些文件都以:

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

我正在使用 DevExpress,但是,查看您的代碼,您必須嘗試:

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

第二種解決方案:無論如何,我還有幾個不同的 ViewModel 來管理其中一些窗口。 在這種情況下,如果我有一些變量要從一個 ViewModel 讀取到另一個,我將這些變量設置為static

例子:

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

也許第二個解決方案更簡單,仍然可以滿足您的需要。

我希望這很清楚。 如果你願意,問我更多細節。

我自己不是 MVVM 專業人士,但我在處理此類問題時遇到的問題是,擁有一個將所有其他視圖模型作為屬性的主類,並將此類設置為所有窗口的數據上下文,我不這樣做不知道它是好是壞,但對於您的情況,這似乎就足夠了。

有關更復雜的解決方案,請參閱

對於更簡單的,

你可以做這樣的事情,

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

現在您必須為傳遞視圖模型的OptionWindow創建另一個構造函數。

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

這是為了確保兩個窗口都在視圖模型的同一個實例上工作。

現在,無論你在哪里打開第二個窗口,都使用這兩行

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

現在您將第一個窗口的視圖模型傳遞給第二個窗口,以便它們在 MainViewModel同一實例上工作。

一切都完成了,只是你必須解決綁定為

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

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

在 MVVM 中,模型是共享數據存儲。 我會在實現INotifyPropertyChangedOptionsModel保留字體大小。 任何對字體大小感興趣的視圖模型都訂閱了PropertyChanged

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

在 FontSize 改變時需要更新的 ViewModel 中:

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

我是 WPF 的新手,我想出了一個解決方案,我很好奇更多知識淵博的人對它的對與錯的想法。

我有一個考試選項卡和一個模板選項卡。 在我的簡單概念證明中,我希望每個選項卡都“擁有”一個Exam對象,並且能夠訪問另一個選項卡的Exam

我將每個選項卡的 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; }
    }
}

然后所有內容都可以在 xaml 中訪問:

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 (摘錄)

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM