I have this view (fragment) with 2 buttons. If I click the left button, View 2 opens. If I click the right button, View 3 opens. I am using Caliburn Micro. Hence, the Button's x:Name value is the name of the View Model's method which is invoked after clicking the button.
View1:
<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>
Here are the 2 methods that get invoked after button click. You can see they have the same name. Now people say that it is prohibited to open a view inside a view model. This is why I am using an IWindowManager instance winmanager inside my methods when I want to open a new view. Instead of creating a new view instance, I create a new viewmodel instance! First question: Is this against the rules of 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");
}
}
Ok, now let's say the ArtikelAuswahl gets invoked. Thanks to Caliburn Micro, View2 shows:
View2:
<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 is data bound to ViewModel2. But before I show you ViewModel2 I want to show you what I can do in View2:
I select 3 articles from a listbox and click on the button on the bottom of view2
to add these articles to a list:
<Button x:Name="FuegeArtikelHinzu" Content="Hinzufügen" Width="100" Height="25"> </Button>
The articles are added to a list in the method FuegeArtikelHinzu
in 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...
}
}
}
And here is my problem! I need the selected articles in the list artikelliste
IN MY VIEW MODEL1!!!
But I ran into a dead end. I heard that this is solved by implementing IMessenger service but I don't understand how it works in my example. What do I need to do to pass the artikelliste
to ViewModel1
according to my example? If you are not familiar with Caliburn Micro, post another solution, either from scratch or with a framework, I don't mind. Thanks in advance for any help!
EDIT:
I can now access ViewModel2 and its Artikelliste
property within ViewModel1. However, I would like to invoke ViewModel1's method ArtikellisteUmformen()
as soon as ViewModel2's Artikelliste
got updated. How do I do that?
This is what I would like to do:
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.