【问题标题】:How to return from target view model to source view model with data collected in target view model and invoke method in source viewmodel如何使用目标视图模型中收集的数据从目标视图模型返回到源视图模型并在源视图模型中调用方法
【发布时间】:2020-05-08 17:51:47
【问题描述】:

我有这个带有 2 个按钮的视图(片段)。如果我单击左侧按钮,视图 2 将打开。如果我单击右键,视图 3 将打开。我正在使用 Caliburn Micro。因此,Button 的 x:Name 值是单击按钮后调用的 View Model 方法的名称。

视图1:

     <StackPanel Name="PnlButtons" 
                Grid.Row="1"  
                Grid.ColumnSpan="2"
                HorizontalAlignment="Center"
                Orientation="Horizontal"
                Opacity="1">
        <Button x:Name="ArtikelAuswahl" 
                Background="Bisque" 
                Content="Artikel auswählen" 
                Width="170" Height="25"
                FontFamily="Verdana">
        </Button>
        <Button x:Name="SonderAuswahl" 
                Background="BlanchedAlmond" 
                Content="Sonderartikel hinzufügen" 
                Width="170" Height="25"
                FontFamily="Verdana">
        </Button>
    </StackPanel>

这是在单击按钮后调用的 2 个方法。您可以看到它们具有相同的名称。 现在人们说禁止在视图模型中打开视图。这就是当我想打开一个新视图时,我在我的方法中使用 IWindowManager 实例 winmanager 的原因。我没有创建新的视图实例,而是创建了一个新的视图模型实例! 第一个问题:这是否违反了 MVVM 的规则?

ViewModel1:

    public class CreateLieferscheinViewModel : Conductor<object>
    {

        private IWindowManager winmanager = new WindowManager();
        public InventurartikelViewModel inventur = new InventurartikelViewModel(); 
        public SonderartikelViewModel sonder = new SonderartikelViewModel();

        public void ArtikelAuswahl()
        {          
            wwinmanager.ShowWindow(inventur, null, null);        
        }

        public void SonderAuswahl()
        {
            winmanager.ShowWindow(sonder,null,null);
        }

        /* ToBeImplemented: Invoke this method once `Artikelliste` is filled!!! */
        public void ArtikellisteUmformen()
        {
            for (int k = 0; k < inventur.Artikelliste.Count; k++)
        {
            Artikelsammlung.Add(new ArtikelModel()); //every selected article will get added to Artikelsammlung
            //get each selected article unfiltered (unformatted)
            Artikelsammlung[k].Bezeichnung = inventur.Artikelliste[k].ToString();
            //Extract the unit out of the Artikel-String
            Artikelsammlung[k].Einheit = Zeichenketten.TextFindenVonBisEnde(Artikelsammlung[k].Bezeichnung, "<", ">");

           //remove "in <Einheit>" from the Artikel-String
            Artikelsammlung[k].Bezeichnung = Zeichenketten.EinheitEntfernen(Artikelsammlung[k].Bezeichnung);
            /* 
             * Bezeichnung and Einheit are now properly formatted...
             */
        }
    }

}

    private ObservableCollection<ArtikelModel> _artikelsammlung;
    public ObservableCollection<ArtikelModel> Artikelsammlung
    {
        get { return _artikelsammlung; }
        set
        {
            _artikelsammlung = value;
            OnPropertyChanged("Artikelsammlung");
        }
    }

好的,现在假设 ArtikelAuswahl 被调用。感谢 Caliburn Micro,View2 显示:

视图2:

<Window x:Class="Lieferscheine.Views.InventurartikelView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:Lieferscheine.Views"
         xmlns:main="clr-namespace:Lieferscheine"
         xmlns:cal="http://www.caliburnproject.org"
         mc:Ignorable="d" Title="Inventurartikel suchen"
         Height="450" Width="370">
<StackPanel Height="423" VerticalAlignment="Bottom">
    <Label Name="lblArtikelbezeichnung" Content="Artikelbezeichnung:" Margin="20, 20, 20, 0"></Label>
    <TextBox Name="BezText" 
             Width="Auto" 
             Margin="20, 0, 20, 0"
             IsEnabled="{Binding Path=BezEnabled}"
             cal:Message.Attach="[Event KeyUp] = [Action KeyUpBez($executionContext)]">
    </TextBox>

    <Label Name="lblLieferant" Content="Lieferant:" Margin="20, 0, 20, 0"></Label>
    <TextBox Name="LiefText" 
             Width="Auto" 
             Margin="20, 0, 20, 0"
             IsEnabled="{Binding Path=LiefEnabled}"                
             cal:Message.Attach="[Event KeyUp] = [Action KeyUpLief($executionContext)]">
    </TextBox>

    <Button Name="SucheArtikel" 
            Content="Suchen" 
            Width="100" Height="25" 
            Margin="20, 10,240, 10">
    </Button>
    <Button x:Name="GesamteListeAnzeigen" 
            Content="Gesamte Liste anzeigen" 
            Width="150" Height="26" 
            Margin="0, -50, 20, 0" 
            HorizontalAlignment="Right"/>
    <main:MultipleSelectionListBox 
             x:Name="LboxAddArtikel"                
             SelectionMode="Multiple" 
             Width="320" Height="220" 
             Margin="20, 10, 20, 10"
             BindableSelectedItems="{Binding Path=MyCollectionOfSelectedIDs}">
    </main:MultipleSelectionListBox>

    <Button x:Name="FuegeArtikelHinzu" 
            Content="Hinzufügen" 
            Width="100" Height="25">
    </Button>
</StackPanel>

View2 是绑定到 ViewModel2 的数据。但在向您展示 ViewModel2 之前,我想向您展示我可以在 View2 中做什么:

我从列表框中选择了 3 篇文章,然后单击 view2 底部的按钮将这些文章添加到列表中:

<Button x:Name="FuegeArtikelHinzu" Content="Hinzufügen" Width="100" Height="25"> </Button>

文章被添加到FuegeArtikelHinzu方法viewmodel2中的列表中:

ViewModel2:

public class InventurartikelViewModel : Screen
{
    private List<string> _artikelliste = new List<string>();
    public List<string> Artikelliste
    {
        get { return _artikelliste; }
        set
        {
            _artikelliste = value;
            OnPropertyChanged("Artikelliste");
        }
    }

    public bool ArtikellisteUpdated()
    {
        Filled = Artikelliste != null ? true : false;

        return Filled;
    }

    private bool _filled;
    public bool Filled
    {
        get 
        { 
            return _filled; 
        }
        set
        {
            _filled = value;
            OnPropertyChanged("Filled");
            if(_filled == true)
            {
                //TO BE IMPLEMENTED
                //then invoke ViewModelA's method `ArtikellisteUmformen()`
            }
            _filled = false; //I believe I would need to set _filled back 

            //to false to prevent overflow. Otherwise ViewModelA's method 

            //`ArtikellisteUmformen()` would get invoked over and over again because 

            //_filled is always true from now on. Is that correct?
        }

    }
    public void FuegeArtikelHinzu()
    {
        try
        {
            //This adds only the multiple selected items to a list
            var multi = MyCollectionOfSelectedIDs;

            int i = 0;
            foreach (string item in multi)
            {
                Artikelliste.Insert(i, item);
                i++;
            }

            MessageBox.Show("Artikel hinzugefügt!"); //ok, all added... 

            //call `ArtikellisteUpdated()` and set Property `Filled` (=`Artikelliste` is filled) to true
            ArtikellisteUpdated(); //true unless null

        }
        catch (Exception e)
        {
            MessageBox.Show(e.Message, "Zuerst Artikel auswählen!"); //you must select an article first...
        }
    }
}

这是我的问题!我需要列表中的选定文章artikelliste IN MY VIEW MODEL1!!! 但是我遇到了死胡同。我听说这可以通过实现 IMessenger 服务来解决,但我不明白它在我的示例中是如何工作的。根据我的示例,我需要做什么才能将artikelliste 传递给ViewModel1?如果您不熟悉 Caliburn Micro,请从头开始或使用框架发布另一个解决方案,我不介意。提前感谢您的帮助!

编辑:

我现在可以在 ViewModel1 中访问 ViewModel2 及其 Artikelliste 属性。但是,我想在 ViewModel2 的 Artikelliste 更新后立即调用 ViewModel1 的方法 ArtikellisteUmformen()。我怎么做? 这就是我想做的:

Artikelliste已被填充时,您可以在 ViewModel2 上调用一个事件,例如 ArtikelListeUpdated。 ViewModel1 侦听该事件并在必要时对其做出反应。您甚至不需要该事件,以防您不必立即对其做出反应。

【问题讨论】:

  • 第一个问题,只要windowManager没有暴露UI元素就不会违反MVVM。如果您想要选定的项目,请在列表框中为每个项目添加一个命令,以将它们添加到您的 ViewModel 的集合中。因为您拥有视图模型的实例,所以您将可以访问这些项目。简单
  • 我更新了我的问题。除了最后一步(要点),我做了@maxx313 的建议。我不知道如何实现事件和监听器。以前从来没有这样做过。能否请你帮忙?请在我给定的项目中实施最后一个要点。这对我来说很重要!
  • 问题更新:现在我需要的最后一步是:当 ViewModel2 的 Property Filled 为 true 时,调用 ViewModel1 的方法 ArtikellisteUmformen()。但我无法从 ViewModel2 访问 VIewModel1。我如何从 VIEWMODEL1 监听到 VIEWMODEL2 的属性FILLED 并在FILLED == TRUE 时调用ARTIKELLISTEUMFORMEN()?????????????????????????

标签: c# wpf mvvm caliburn.micro


【解决方案1】:
  1. 创建 ViewModel 实例并使用 WindowManager 接口并不违反 MVVM。你应该没问题。

  2. 我不熟悉 Caliburn 微框架。但是这个呢:

    • artikelliste 作为属性保存在 ViewModel2 中。
    • ViewModel2 的实例作为属性保存在 ViewModel1 中。在调用 WindowManager.ShowWindow 方法时传递该实例。
    • 然后,当 artikelliste 被填充后,您可以在 ViewModel2 上调用一个事件,例如 ArtikelListeUpdatedViewModel1 监听该事件并在必要时对其做出反应。您甚至不需要该事件,以防您不必立即做出反应。

编辑:最后一步的示例:

public class ViewModel1 {
    public ViewModel2 ChildVm {get;} = new ViewModel2();

    public ViewModel1() {
        ChildVm.Updated += OnChildUpdated;
    }

    private void OnChildUpdated(object pSender, EventArgs pArgs) {
        // do what is needed
    }
}

public class ViewModel2 {
    public event EventHandler Updated;

    public void DoStuff()
    {
        // do something

        if (Updated != null)
            Updated.Invoke(this, EventArgs.Empty);
    }
}

请注意,在这种情况下,ViewModel2 具有对 ViewModel1 的内部引用,因此可以防止 ViewModel1 被垃圾回收。

我建议您在继续您的项目之前查看 C# 的 EventHandling 的基础知识:Understanding events and event handlers in C#

【讨论】:

  • "然后,当 artikelliste 被填满时,您可以在 ViewModel2 上调用一个事件,例如 ArtikelListeUpdated。ViewModel1 监听该事件并在必要时对其作出反应。"请您为此添加一个示例吗?这是我唯一缺少的部分。一旦 artikelliste 被填满,我需要在 ViewModel1 中调用一个方法。
  • 天哪!这是工作!!! 5天后,非常感谢您,先生!
猜你喜欢
  • 1970-01-01
  • 2015-05-02
  • 1970-01-01
  • 2011-01-28
  • 2017-10-04
  • 2013-05-11
  • 2013-10-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多