WPF MVVM - przekazywanie danych pomiędzy OknemGłównym a przełączanymi UserControl's

0

Na oknie mam m.in. kontrolki:

  • ListView - w niej trzymam kolekcje artykulow
  • StackPanel w ktorym jest szereg kontrolek Label i TextBlock, w tych kontrolkach chce wyswietlac wlasciwosci obiektu zaznaczonego w ListView
  • Grid.Name="OszarGlowny" - obszar w ktorym przelaczam poprzez opcje menu "podwidoki" wykonane z UserControl

Pytania:
1) Jak ustawic dla StackPanel'a czy raczej zawartych w nim TextBlock'ow zrodlo danych z ViewModela "ZaznaczonyArtykulWLiscie "?
(ale nie poprzez "void ListView_Artykuly_SelectionChanged(object sender, SelectionChangedEventArgs e)", chcialbym to zrobic w samym XAML'u albo co najwyzej w "ArtykulyScalaVM"

2) Jak sensownie przekazywac obiekt zaznaczony w ListView, do podwidoków w momencie wybrania z menu i zaladowania takiego podwidoku w oknie glownym? (zalezy mi zeby jak najmniej miec codebehind)

    public class ArtykulyScalaVM : INotifyPropertyChanged 
    {
         private ArtykulScalaVM zaznaczonyArtykulWLiscie;
        public ArtykulScalaVM ZaznaczonyArtykulWLiscie 
        { 
            get { return zaznaczonyArtykulWLiscie; } 
            set { zaznaczonyArtykulWLiscie = value;
                  OnPropertyChanged(nameof(ZaznaczonyArtykulWLiscie)); }  
        }
         public ObservableCollection<ArtykulScalaVM> PobraneArtykulyZlozone { get; } = new ObservableCollection<ArtykulScalaVM>();
    }
<Window.DataContext>
        <dataC:ArtykulyScalaVM/>
    </Window.DataContext>
......

<ListView Margin="2"  x:Name="ListView_Artykuly" 
                                  ItemsSource="{Binding PobraneArtykulyZlozone}" 
                                  SelectedItem="{Binding ZaznaczonyArtykulWLiscie}">

......

<StackPanel DockPanel.Dock="Top" 
                                            Visibility="{Binding ElementName=ToggleMenuBtn, 
                                                        Path=IsChecked, 
                                                        Converter={StaticResource BoolToWidthConverter}}">
.........
<Label Grid.Column="0" Grid.Row="0" HorizontalAlignment="Right" VerticalAlignment="Center" FontSize="11">Index:</Label>
                                        <TextBlock Text="{Binding Index}" 
                                            VerticalAlignment="Center" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Left" FontWeight="Bold" Margin="3,0,0,0"/>
                                        <Label Grid.Column="0" Grid.Row="1" Margin="33,0,0,0" FontSize="11" VerticalAlignment="Center">Opis:</Label>
                                        <TextBlock  Text="{Binding PelnyOpis}" TextWrapping="Wrap" 
                                            Margin="3,5,0,1" VerticalAlignment="Top" Grid.RowSpan="2" 
                                            Grid.Column="1" Grid.Row="1" HorizontalAlignment="Left" />
..........
                                <Grid Name="ObszarGlowny">
                                        <ContentControl Grid.Column="0" Content="{Binding AktywnyWidokUC}"></ContentControl>
                                </Grid>
.........
0

pytanie 1) juz nieaktualne, ustawilem DataContext w StackPanelu jak nizej i dziala.

<StackPanel DockPanel.Dock="Top" 
                                            Visibility="{Binding ElementName=ToggleMenuBtn, 
                                            Path=IsChecked, 
                                            Converter={StaticResource BoolToWidthConverter}}"
                                            DataContext="{Binding ZaznaczonyArtykulWLiscie}">

pozostaje pytanie 2)

do tej pory dziala mi to przelaczanie podwidokow w ten sposob, ze w konstruktorze WidokuModelu ktory jest ustawiony jako DataContext w MainWindow tworze kolekcje swoich UserControl, i potem w zaleznosci co wybiore z menu podmieniam aktywny podwidok we wlasciwosci.
kazdy podwidok ma swoj ViewModel, i teraz wlasciwe pytanie jak przekazywac zaznaczony obiekt w ListView w oknie glownym do podwidoku a raczej VieModelu tego podidoku zeby to bylo zsynchronizowane wszystko?

    public class ArtykulyScalaVM : INotifyPropertyChanged
    {
private UCWidokBazowy aktywnyWidokUC;
        public UCWidokBazowy AktywnyWidokUC
        {
            get { return aktywnyWidokUC; }
            set {
                aktywnyWidokUC = value;
                OnPropertyChanged(nameof(AktywnyWidokUC));  }
        }
        private IEnumerable<UCWidokBazowy> DostepneWidokiUC { get; set; }

        private ICommand ustawWidokCmd;
        public ICommand UstawWidokCmd 
        { 
            get
            {
                if (ustawWidokCmd==null)
                    ustawWidokCmd = new RelayCommand(KlikniecieOpcjiMenu);
                return ustawWidokCmd;
            }
        }
        private void KlikniecieOpcjiMenu(object obj)
        {
            AktywnyWidokUC = DostepneWidokiUC.First(x => x.Title == (string)obj);
        }
public ArtykulyScalaVM()
        {
            this.PrzygotujPodwidokiUstawDefaultowy();
        }
        
        private void PrzygotujPodwidokiUstawDefaultowy()
        {
            DostepneWidokiUC = new List<UCWidokBazowy>()
            {
                new UC_default(),
                new CzasyWydanView(),
                new PodmianaStanowiska(),
                new ZamianaMarszrutMiejscami(),
                new ZwijanieMarszrut(),
                new EdycjaSynchronicznaMarszrutWrazZBOM(),
                new CzasyNakladaniaView()
            };
            AktywnyWidokUC = DostepneWidokiUC.FirstOrDefault();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string nazwaWlasciwosci)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(nazwaWlasciwosci));
        }
    }

public partial class CzasyWydanView : UserControl, UCWidokBazowy
    {
        public string Title { get; set; }

        public CzasyWydanView()
        {
            InitializeComponent();
            Title = nameof(CzasyWydanView);
        }
    }

a w XAML mam tak:

<MenuItem Header="Podmiana Stanowiska" Style="{StaticResource StylJasnyFioletowy}"
                          Command="{Binding UstawWidokCmd}" CommandParameter="PodmianaStanowiska"/>
                <MenuItem Header="Czasy wydań" Style="{StaticResource StylJasnyFioletowy}"
                          Command="{Binding UstawWidokCmd}" CommandParameter="CzasyWydanView"/>
                <MenuItem Header="Zamiana marszrut miejscami" Style="{StaticResource StylJasnyFioletowy}"
                          Command="{Binding UstawWidokCmd}" CommandParameter="ZamianaMarszrutMiejscami"/>
                <MenuItem Header="Zwijanie marszrut" Style="{StaticResource StylJasnyFioletowy}"
                          Command="{Binding UstawWidokCmd}" CommandParameter="ZwijanieMarszrut"/>
                <MenuItem Header="Edycja marszruty wraz z BOM" Style="{StaticResource StylJasnyFioletowy}"
                          Command="{Binding UstawWidokCmd}" CommandParameter="EdycjaSynchronicznaMarszrutWrazZBOM"/>
1

Ad 2.
Możesz zrobić tak, że w set właściości AktywnyWidokUC wywołujesz jakąś metodę, w której będziesz mieć coś w stylu AktywnyWidokUC.Load( wybrana pozycja z list view );
A jak masz już wybraną pozycje w menu i zmieniasz pozycje z ListView to analogicznie - wywołujesz coś w stylu AktywnyWidokUC.Load( tutaj nowo wybrana pozycja z list view);

PS Jeśli można zapytać, czemu piszesz po polsku?

0

To wyjście w którym mieszam widok i jego codebehind do tej logiki, a jest jakiś sposób żeby ViewModele się wymieniały tymi danymi?
**Wątek obok **poruszyłem temat gdzie w WPF tworzyć zależności między obiektami bo dobrze byłoby mieć jedno miejsce na to.

Edit: @Piotr.NET a żeby właściwość "AktywnyWidokUC" zrobić słownikiem gdzie widok będzie kluczem a wartością ViewModel tego widoku, okiem bardziej doświadczonego programisty podpowiedź jak to widzisz?

moze bardziej obrazowo bedzie latwiej mi wytlumaczyc o co walcze:
to widok glowny :
widokOgolny.PNG

a to z zaladowanym jednym z podwidokow "narzedziowych":
podwidok.PNG

chcialbym osiagnac efekt:
a) przekazywac dynamicznie do wybranego/aktywnego podwidoku zaznaczony ArtykulScalaVM z lewej ListView, dynamicznie tzn jakzaznacze jeden od razu jest widoczny w prawym podwidoku, jezeli w ListView zaznacze inny to w prawym podwidoku usuwany jest poprzedni i ladowany aktualnie zaznaczony
b) przekazywac dynamicznie kolekcje zaznaczonych artykulow w ListView do podwidoku, zeby dodawanie/usuwanie bylo sprzezone ze zdarzeniem/poleceniem IsChecked, a bardziej z wlasciwoscia ArtykulScalaVM.CzyZaznaczony.
(tutaj to generalnie nie wiem jak budowac taka kolekcje zaznaczonych artykulow w ViewModel okna glownego, pierwszy pomysl to w OnPropertyChangesprawdzac jaka wlasciwosc sie zmienia i jezeli jest to "CzyZaznaczony" to dodawac polecenie ktore doda lub usunie zaznaczony artykul z ListView i tomialobybyc w ArtykulScalaVM ale zeby budowac taka kolekcje w ArtykulyScalaVM to musialbymmiec referencje ArtykulyScalaVM w ArtykulScalaVM - tak jest dobrze? tak się robi?)

kod ktory pokazuje fragmenty modelu i modelu widoku ponizej, ViewModelu dla podwidoku jeszcze nie zaczalem nawet, na razie ogarniam model i logike przeliczania:

 public class ArtykulScalaVM : INotifyPropertyChanged
    {
        private bool czyZaznaczony;
        private ArtykulScala artykul;
        public bool CzyZaznaczony 
        { 
            get { return czyZaznaczony; } 
            set { czyZaznaczony = value; 
                OnPropertyChanged(nameof(CzyZaznaczony)); }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(params string[] nazwyWlasciwosci)
        {
            if (PropertyChanged != null)
                foreach (string nazwaWlasciwosci in nazwyWlasciwosci)
                    PropertyChanged(this, new PropertyChangedEventArgs(nazwaWlasciwosci));
        }

        private ICommand zaznaczArtykulCmd;
        public ICommand ZaznaczArtykulCmd
        {
            get
            {
                if (zaznaczArtykulCmd == null)
                {
                    zaznaczArtykulCmd = new RelayCommand(
                        o => { czyZaznaczony = true; OnPropertyChanged(nameof(CzyZaznaczony)); },
                        o => { return !CzyZaznaczony; });
                }
                return zaznaczArtykulCmd;
            }
        }

        public ArtykulScalaVM(ArtykulScala artykul)
        {
            this.artykul = artykul;
        }
        public string Index { get { return artykul.Index; } }
        public string PelnyOpis { get { return artykul.PelnyOpis; } }
}

 public class ArtykulScala
    {
        public ArtykulScala()
        {
            //CzyZaznaczony = false;
        }

        //public bool CzyZaznaczony { get; set; }

        public string Index { get; set; }
        public string PelnyOpis { get; set; }
        public string RodzajKontroli { get; set; }

        public string GrupaWyrobowaText { get; set; }
        public string ZalozycielIndexu { get; set; }

        public DateTime DataZalozenia { get; set; }
        public DateTime DataEdycji { get; set; }

}
1

Nie zanotowałem, że klasa z UC to UserControl - widzisz zaoszczędziłeś 9 znaków, a czytający może nie zwrocić uwagi co to jest. Tak samo z VM to dev ops wpadnie na to, że to Virtual Machine :D
Ale do rzeczy:

  • tworzysz bazowy view model, po którym dziedziczą viewmodele konkretnych pozycji menu
  • w głównym viewmodelu tworzysz listę od typu bazowego i dodajesz do niej utworzone viewmodele oraz tworzysz właściwość typu bazowego viewmodelu, aby przechować aktualny wybrany viewmodel
  • w xaml tworzysz ItemsControl z listą viewmodeli jako ItemSource, ustawiasz ItemTemplate jako np. button, a w nim komende z bindowaną do głównego modelu (przez proxy albo FindeAncestor), przez parametr komendy bindujesz obiekt z listy (po prostu CommandParameter="{Binding}")
  • w bazowym viewmodel komenda wyżej wspomnianego buttona ustawia aktualny wybrany viewmodel z parametru tej komendy
  • w xaml tworzysz ContentControl i do niego bindujesz aktualnie wybrany widok
  • w xaml w sekcji Resources dodajesz kolejne DataTemplate z DataType odpowiadającemu każdemu viewmodelu z menu.
  • a z tym ładowaniem zawartości do wybranej pozycji z menu to jak wyżej, na set wykonujesz jakąś metode na aktualnie wybranym menu i przekazujesz parametry

Sory, że tak chaotycznie, mam nadzieję, że coś ogarniesz :D
Jak będą problemy to pytaj, później znajdę chwilę to skleję jakiś przykład z tym co próbuję przekazać :)

0

chyba się zakrecilem juz totalnie ;) nie jarzę juz gdzie widok a gdzie model-widoku czy ktos ma jakis przyklad do podejrzeia jak jest zorganizowana aplikacja WPF MVVM gdzie jest:

  • widok glowny
  • jest jakis sposob przelaczania "podwidokow" w oknie glownym?
  • są przekazywane obiekty pomiedzy widokiem glownym a podwidokami (czy viewmodelem glownym i viewmodelami podwidokow)
  • te przekazywane obiekty sa non stop synchronizowane

Edit:
Tutaj znalazlem przyklad w necie czegos podobnego - to co polskie to dodane ode mnie, TextBox.Text na oknie glownym ma byc przenoszony do aktywnego podwidoku - nie mam pojecia jak to zrobic.

Debuguje, stawiam kropki w setterach wlasciwosci viewmodeli i tam to sie ustawia, a na oknie, na podwidoku nie widze zmiany

Widok główny

<Window x:Class="ConfigurationDialogExample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ViewModel="clr-namespace:ConfigurationDialogExample.ViewModel"
    xmlns:View="clr-namespace:ConfigurationDialogExample.View"
    Title="Setup" Height="300" Width="500">
    
    <Window.Resources>
        <DataTemplate DataType="{x:Type ViewModel:GeneralSettingsViewModel}">
            <View:GeneralSettingsView/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type ViewModel:AdvancedSettingsViewModel}">
            <View:AdvancedSettingsView/>
        </DataTemplate>
    </Window.Resources>
    
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="3*"/>
        </Grid.ColumnDefinitions>
        <StackPanel Grid.Column="0" Orientation="Vertical">
        <ListBox x:Name="ListBoxMenu"                    
                 Grid.Column="0" Margin="5"
                 ItemsSource="{Binding Settings}"
                 SelectedIndex="0">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" Padding="10"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
         <!-- <TextBox Text="{Binding TextOknoGlowne}"/> -->
        <TextBox Text="ten tekst ma sie przeniesc jako zrodlo danych do podwidokow i byc synchronizowany od razu w podwidoku przy kazdej zmianie na oknie glownym"/>
        </StackPanel>
        <Border Grid.Column="1" Margin="5" BorderBrush="#FF7F9DB9" BorderThickness="1">
            <ContentControl Content="{Binding ElementName=ListBoxMenu, Path=SelectedItem}"/>
        </Border>
    </Grid>
</Window>

ViewModelGlowny

public class ConfigurationDialogViewModel : ViewModelBase
    {
        private readonly ObservableCollection<SettingsViewModelBase> settings;
        public ObservableCollection<SettingsViewModelBase> Settings
        {
            get { return this.settings; }
        }

        public ConfigurationDialogViewModel()
        {
            this.settings = new ObservableCollection<SettingsViewModelBase>();
            this.settings.Add(new GeneralSettingsViewModel());
            this.settings.Add(new AdvancedSettingsViewModel());
        }


        private string textOknoGlowne;
        public string TextOknoGlowne
        {
            get { return textOknoGlowne; }
            set { textOknoGlowne = value;
                AktualizujPodwidoki(value);
                OnPropertyChanged(nameof(TextOknoGlowne)); }
        }
        private void AktualizujPodwidoki(string text)
        {
            foreach (var item in settings)
                item.NazwaPodokno = text;
        }
    }

Widoki podwidokow bardzo uproszczone

UserControl x:Class="ConfigurationDialogExample.View.GeneralSettingsView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:dc="clr-namespace:ConfigurationDialogExample.ViewModel"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <UserControl.DataContext>
        <dc:GeneralSettingsViewModel/>
    </UserControl.DataContext>
    <StackPanel Orientation="Vertical">
        <TextBlock Text="General settings" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        <TextBox Text="{Binding NazwaPodokno}"/>
    </StackPanel>
</UserControl>

ViewModele podwidokow

public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

public abstract class SettingsViewModelBase : ViewModelBase
    {
        public abstract string Name { get; }

        private string nazwaPodokno;
        public string NazwaPodokno
        {
            get { return nazwaPodokno; }
            set { nazwaPodokno = value;
                OnPropertyChanged(nameof(NazwaPodokno)); }
        }
    }

public class GeneralSettingsViewModel : SettingsViewModelBase
    {
        public override string Name
        {
            get { return "General"; }
        }
    }
0

tutaj jest moj uproszczony projekt na ktorym widac co chce osiagnac, no i ponownie sie zatkalem. Prosilbym o pomoc.
https://github.com/Varran/SzkieletAplikacjiMainViewPlusPodwidoki

Niby z viewmodelu glownego juz przekazuje 'ZaznaczonyNaListViewJakisObiekt' do viewmodelu podwidoku, ale zauwazylem że w podwidoku nie odpala mi sie 'OnPropertyChanged(nameof(ObiektZaznaczonyNaGlownymOknie))' bo 'public event PropertyChangedEventHandler PropertyChanged;' jest null, nie rozumiem dlaczego.

public class OknoGlowneViewModel : ViewModelBase
    {
        private IPodOknoViewModel biezacyViewModel;
        public IPodOknoViewModel BiezacyViewModel
        {
            get { return biezacyViewModel; }
            set { biezacyViewModel = value;
                OnPropertyChanged(nameof(BiezacyViewModel)); }
        }

        private readonly ObservableCollection<IPodOknoViewModel> podOknaViewModels;
        public ObservableCollection<IPodOknoViewModel> PodOknaViewModels { get { return this.podOknaViewModels; } }

        private JakisObiekt zaznaczonyNaListViewJakisObiekt;
        public JakisObiekt ZaznaczonyNaListViewJakisObiekt
        {
            get { return zaznaczonyNaListViewJakisObiekt; }
            set
            {
                zaznaczonyNaListViewJakisObiekt = value;
                BiezacyViewModel.ObiektZaznaczonyNaGlownymOknie = value;   //tutaj aktualizuje mi sie wlasciwosc w podwidoku, ale w podwidoku nie odpala mi sie 'OnPropertyChanged(nameof(ObiektZaznaczonyNaGlownymOknie))' bo w podwidoku 'public event PropertyChangedEventHandler PropertyChanged;' jest null, nie rozumiem dlaczego.
                OnPropertyChanged(nameof(ZaznaczonyNaListViewJakisObiekt));
                OnPropertyChanged(nameof(BiezacyViewModel));
            }
        }
//(.....)
    }
public class PodOknoDwaViewModel : ViewModelBase, IPodOknoViewModel
    {
        public string Name { get { return "PodOkno ViewModel Dwa"; } }

        protected string nazwaPodokno;
        public string NazwaPodokno
        {
            get { return nazwaPodokno; }
            set
            {
                nazwaPodokno = value;
                OnPropertyChanged(nameof(NazwaPodokno));
            }
        }

        protected JakisObiekt obiektZaznaczonyNaGlownymOknie;
        public JakisObiekt ObiektZaznaczonyNaGlownymOknie
        {
            get { return obiektZaznaczonyNaGlownymOknie; }
            set
            {
                obiektZaznaczonyNaGlownymOknie = value;
                OnPropertyChanged(nameof(ObiektZaznaczonyNaGlownymOknie));
            }
        }

        //public string NazwaObiektuZaznaczonegoNaGlownymOknie { get { return obiektZaznaczonyNaGlownymOknie.NazwaObiektu; } }

        private ObservableCollection<JakisObiekt> kolekcjaJakisObiektowZaznaczonychwListViewNaOknieGlownym;
        public ObservableCollection<JakisObiekt> KolekcjaJakisObiektowZaznaczonychwListViewNaOknieGlownym 
                        { get { return this.kolekcjaJakisObiektowZaznaczonychwListViewNaOknieGlownym; } }

    }

public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string nazwaWlasciwosci)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(nazwaWlasciwosci));
        }
    }

public interface IPodOknoViewModel
    {
        string Name { get; }
        string NazwaPodokno { get; set; }

        JakisObiekt ObiektZaznaczonyNaGlownymOknie    { get; set; }
        ObservableCollection<JakisObiekt> KolekcjaJakisObiektowZaznaczonychwListViewNaOknieGlownym { get; }
    }
public class JakisObiekt
    {
        public bool CzyZaznaczony { get; set; }
        public string NazwaObiektu { get; set; }
        public int StanObiektu { get; set; }

        public JakisObiekt(string nazwa, int stan, bool check)
        {
            CzyZaznaczony = check;
            NazwaObiektu = nazwa;
            StanObiektu = stan;
        }

        public override string ToString()
        {
            return $"{NazwaObiektu}; {StanObiektu}; {CzyZaznaczony}";
        }
    }
1

Po ostrych dwóch dobach szperania, doczytywania, rozmów z samym sobą na forum działa dokładnie tak jak chciałem.

Dwa wnioski do których dogrzebałem się dopiero po dwóch dobach:
1. Jezeli ViewModel ktory implementuje 'INotifyPropertyChanged' ma caly czas 'PropertyChanged' rowne null to pomimo aktualizowania się wlasciwosci w ViewModelu nic nie bedzie widoczne na widoku.
2. A 'PropertyChanged' w ViewModelu bedzie zawsze null jezeli ViewModel bedzie tworzony i w konstruktorze MainViewModelu ktory go tworzy oraz w XAML'u widoku tego ViewModelu deklarowanym jako UserControl.DataContext.

GitHub /master juz jest finalnie dzialajacy/:
https://github.com/Varran/SzkieletAplikacjiMainViewPlusPodwidoki

1

Super, że się udało. Tutaj masz fajny przykład projektu MVVM zrobiony przez sam Microsoft.
https://github.com/microsoft/InventorySample
W tym projekcie np. jest to o co mi chodziło na początku z metodą Load w viewmodelach :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1