如何使用目标视图模型中收集的数据从目标视图模型 return 到源视图模型并调用源视图模型中的方法
How to return from target view model to source view model with data collected in target view model and invoke method in source viewmodel
我有这个带有 2 个按钮的视图(片段)。如果我单击左键,将打开视图 2。如果我单击右键,将打开视图 3。我正在使用 Caliburn Micro。因此,按钮的 x:Name 值是单击按钮后调用的视图模型方法的名称。
视图 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 中做什么:
我从列表框中 select 3 篇文章,然后单击 view2
底部的按钮将这些文章添加到列表中:
<Button x:Name="FuegeArtikelHinzu"
Content="Hinzufügen"
Width="100" Height="25">
</Button>
viewmodel2
中 FuegeArtikelHinzu
方法将文章添加到列表中:
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
中的 selected 文章 IN MY VIEW MODEL1!!!
但是我运行进了死胡同。我听说这可以通过实施 IMessenger 服务来解决,但我不明白它在我的示例中是如何工作的。根据我的示例,我需要做什么才能将 artikelliste
传递给 ViewModel1
?如果您不熟悉 Caliburn Micro,post 另一种解决方案,无论是从头开始还是使用框架,我都不介意。在此先感谢您的帮助!
编辑:
我现在可以在 ViewModel1 中访问 ViewModel2 及其 Artikelliste
属性。但是,我想在 ViewModel2 的 Artikelliste
更新后立即调用 ViewModel1 的方法 ArtikellisteUmformen()
。我怎么做?
这就是我想要做的:
When the Artikelliste
has been filled, you can invoke an event on ViewModel2, for example ArtikelListeUpdated. ViewModel1 listens to that event and reacts to it if necessary. You even don't need the event in case you don't have to react to it immediately.
创建 ViewModel 实例并使用 WindowManager 接口并不反对 MVVM。你应该没问题。
我对 Caliburn Micro Framework 不熟悉。但是这个呢:
- 在 ViewModel2 中将 artikelliste 作为 属性。
- 将 ViewModel2 的实例保存为 ViewModel1 中的 属性。您在调用 WindowManager.ShowWindow 方法时传递该实例。
- 然后,当 artikelliste 被填满后,您可以在 ViewModel2 上调用一个事件,例如 ArtikelListeUpdated 。 ViewModel1 侦听该事件并在必要时对其作出反应。您甚至不需要该事件,以防您不必立即对其做出反应。
编辑: 最后一步的示例:
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# 事件处理的基础知识:Understanding events and event handlers in C#
我有这个带有 2 个按钮的视图(片段)。如果我单击左键,将打开视图 2。如果我单击右键,将打开视图 3。我正在使用 Caliburn Micro。因此,按钮的 x:Name 值是单击按钮后调用的视图模型方法的名称。
视图 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 中做什么:
我从列表框中 select 3 篇文章,然后单击 view2
底部的按钮将这些文章添加到列表中:
<Button x:Name="FuegeArtikelHinzu" Content="Hinzufügen" Width="100" Height="25"> </Button>
viewmodel2
中 FuegeArtikelHinzu
方法将文章添加到列表中:
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
中的 selected 文章 IN MY VIEW MODEL1!!!
但是我运行进了死胡同。我听说这可以通过实施 IMessenger 服务来解决,但我不明白它在我的示例中是如何工作的。根据我的示例,我需要做什么才能将 artikelliste
传递给 ViewModel1
?如果您不熟悉 Caliburn Micro,post 另一种解决方案,无论是从头开始还是使用框架,我都不介意。在此先感谢您的帮助!
编辑:
我现在可以在 ViewModel1 中访问 ViewModel2 及其 Artikelliste
属性。但是,我想在 ViewModel2 的 Artikelliste
更新后立即调用 ViewModel1 的方法 ArtikellisteUmformen()
。我怎么做?
这就是我想要做的:
When the
Artikelliste
has been filled, you can invoke an event on ViewModel2, for example ArtikelListeUpdated. ViewModel1 listens to that event and reacts to it if necessary. You even don't need the event in case you don't have to react to it immediately.
创建 ViewModel 实例并使用 WindowManager 接口并不反对 MVVM。你应该没问题。
我对 Caliburn Micro Framework 不熟悉。但是这个呢:
- 在 ViewModel2 中将 artikelliste 作为 属性。
- 将 ViewModel2 的实例保存为 ViewModel1 中的 属性。您在调用 WindowManager.ShowWindow 方法时传递该实例。
- 然后,当 artikelliste 被填满后,您可以在 ViewModel2 上调用一个事件,例如 ArtikelListeUpdated 。 ViewModel1 侦听该事件并在必要时对其作出反应。您甚至不需要该事件,以防您不必立即对其做出反应。
编辑: 最后一步的示例:
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# 事件处理的基础知识:Understanding events and event handlers in C#