Laden...

MVVM: Regions / RegionManager

Erstellt von manullino vor 14 Jahren Letzter Beitrag vor 14 Jahren 3.944 Views
manullino Themenstarter:in
371 Beiträge seit 2008
vor 14 Jahren
MVVM: Regions / RegionManager

Hallo zusammen,

Gerade bin ich mit dem Entwurf meines Basislayoutes fuer meine zukünftigen Applikationen beschäftigt. Das Ganze soll mit WPF/Silverlight und dem MVVM Pattern (Stichwort: Composite Application Guidance, aka: Prism) realisiert werden.

Dazu habe ich mir folgenden Aufbau ausgedacht. Die Applikation besteht aus einem Basis Fenster/UserControl. Diese ist in mehrere Regions aufgeteilt. (im Bild grün markiert) . Das Control ist soweit schon fertig und funktioniert ganz gut.

Meine aktuelle Herausforderung besteht in der MainRegion. Die Main Region ist ein Region des Basis UserControls, wobei das UserControl der MainRegion wieder eigene Regions enthaelt (rot markiert). Der Inhalt (UserControl) der MainRegion soll sich, je nachdem welcher Button gedruckt wurde, ändern. (In diesem Falls ist Button 3 angeklickt worden.)

Wenn ein anderer Button geklickt wird, soll dann das entsprechende UserControl (in der MainRegion) angezeigt werden. Das andere UserControl, sollte im Hintergrund aber aktiv bleiben.

Dann gibt es natürlich UserControls (in der MainRegion) von denen mehrere Instanzen geladen werden dürfen, andere eben nicht. (wie bei normalen Fenster eben auch. Stichwort: Modal)

Kann mir jemand sagen, wie dies mit Prism lösen kann. Wenn ich das richtig verstehe brauchte ich dann für jeden Button (bzw. UserControl welches durch klicken des Button aktivert, gestartet wird) einen eigenen RegionManager. Das Basis UserControl byzw. Die Applikation selber hat natürlich auch einen Region Manager.

Und wie kann ich das Handling zwischen den diversen, geöffneten UserControls in der MainRegion lösen? Gibt es da schon einen Ansatz?

Vielen Dank,
Manullino

446 Beiträge seit 2004
vor 14 Jahren

Schaut mal im IRC vorbei:
Server: https://libera.chat/ ##chsarp

manullino Themenstarter:in
371 Beiträge seit 2008
vor 14 Jahren

Hi,

die meisten dieser Einleitungen und HowTo's, die durch Netz geistern habe ich durchgearbeitet oder zumindest gelesen.

Leider ist der Groschen im Zusammenhang mit den Regions noch nicht gefallen. Also falls jemand damit praktische Erfahrung hat und mein Anliegen nachvollziehen kann und mir dabei unter die Arme greifen kann, waere ich seeeehr dankbar.

Nichtsdestotrotz, werde ich den MSDN Link nochmals genauer betrachten.

Gruesse,
Manullino

manullino Themenstarter:in
371 Beiträge seit 2008
vor 14 Jahren
SingleActive Region

Es geht noch immer um das obrige Thema:

Nun habe ich zwei Views/Module (HomeDashboard und AppointmentDashboard) fuer meine MainRegion erstellt. Ueber Commands startet/aktiviert nun Button 1 das HomeDashboard und Button 2 das AppointmentDashboard. (siehe Skizze oben).

Nun habe ich aber das Problem, dass beide Views untereinander angezeigt werden. Jedoch moechte ich nur die zuletzt gewaehlt sehen.

Muss man dann die MainRegion als SingleActiveRegion defnieren? Oder stellt man besser die andere Region in den Hintergrund? Und wo muesste man das dann zuweisen?

Hier mal das ViewModel der Toolbar:

public class MenuViewModel : IMenuViewModel
    {
        public IMenuView View { get; private set; }

        // Interface for Container and RegionManager of MainRegion (Dashboards)
        private IUnityContainer _container;
        private IRegionManager _regionManager;

        // Delegate for the ClickCommand in the XAML Code.
        public DelegateCommand<object> GoToHomeDashboard { get; set; }
        public DelegateCommand<object> GoToAppointmentDashboard { get; set; }

        public MenuViewModel(IMenuView view, IRegionManager regionManager, IUnityContainer container)
        {
            _container = container;
            _regionManager = regionManager;
            View = view;
            View.Model = this;

            // Home Dashboard
           _container.RegisterType<IHomeDashboardView, HomeDashboardView>(new ContainerControlledLifetimeManager());
           _container.RegisterType<IHomeDashboardViewModel, HomeDashboardViewModel>(new ContainerControlledLifetimeManager());

           GoToHomeDashboard = new DelegateCommand<object>(OnGoToHomeDashboard);
            
            //Show the home view on startup!
            _regionManager.RegisterViewWithRegion(ShellRegionNames.MainRegion,
            () => _container.Resolve<IHomeDashboardViewModel>().View);

            // Appointment Dashboard
           _container.RegisterType<IAppointmentDashboardView, AppointmentDashboardView>(new ContainerControlledLifetimeManager());
           _container.RegisterType<IAppointmentDashboardViewModel, AppointmentDashboardViewModel>(new ContainerControlledLifetimeManager());

           GoToAppointmentDashboard = new DelegateCommand<object>(OnGoToAppointmentDashboard);
        }

        // Method to show the Home Dashboard.
        private void OnGoToHomeDashboard(object parameter)
        {
            IRegion mainRegion = _regionManager.Regions[ShellRegionNames.MainRegion];
            IHomeDashboardView dashboardView = _container.Resolve<IHomeDashboardViewModel>().View;

            if (mainRegion.Views.Contains(dashboardView) == false)
            {
                mainRegion.Add(dashboardView);
            }
            // Activate the Region
            mainRegion.Activate(dashboardView);
        }

        // Method to show the Appointment Dashboard.
        private void OnGoToAppointmentDashboard(object parameter)
        {
            IAppointmentDashboardView dashboardView = _container.Resolve<IAppointmentDashboardViewModel>().View;
            IRegion mainRegion = _regionManager.Regions[ShellRegionNames.MainRegion];

            if (mainRegion.Views.Contains(dashboardView) == false)
            {
                mainRegion.Add(dashboardView);
            }
            //// Activate the Region
            mainRegion.Activate(dashboardView);
        }
    }

Vielen Dank,
Manullino

143 Beiträge seit 2008
vor 14 Jahren

An was für ein Element ist denn die MainRegion geknüpft?
Ich benutze ein TabControl. Du scheinst eher ein einfaches ContentControl zu benötigen. Da sollte es glaub ich so funktionieren wie es bei dir im c# code steht, wenn ich das jetzt so Überblicke.

Modale Anzeigen händele ich so:


<Window x:Class="AAAA.Shell"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:cal="http://www.codeplex.com/CompositeWPF" 
    Title="AAAA" MinWidth="900" MinHeight="600" WindowStartupLocation="CenterScreen" WindowState="Maximized">
    <Window.Resources>
        <ResourceDictionary Source="ShellResources.xaml" />
    </Window.Resources>
    <ContentControl>
        <ContentControl.ContentTemplate>
            <DataTemplate>

                <Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext}">
                    <ContentControl x:Name="MainWPF">
....................
                    </ContentControl>
                    <ContentControl x:Name="ModalWindow" Content="{Binding FirstViewOfModalRegion}">
                        <ContentControl.ContentTemplate>
                            <DataTemplate>
                                <Grid>
                                    <Border Background="#90000000" Visibility="{Binding Visibility}">
                                        <Border BorderBrush="Black" BorderThickness="1" Background="AliceBlue" 
					                        CornerRadius="10,0,10,0" VerticalAlignment="Center"
					                        HorizontalAlignment="Center">
                                            <Border.BitmapEffect>
                                                <DropShadowBitmapEffect Color="Black" Opacity="0.5" Direction="270" ShadowDepth="0.7" />
                                            </Border.BitmapEffect>
                                            <ContentControl Content="{Binding View}"  Margin="10">
                                            </ContentControl>
                                        </Border>
                                    </Border>
                                </Grid>
                            </DataTemplate>
                        </ContentControl.ContentTemplate>
                    </ContentControl>
                </Grid>
                <DataTemplate.Triggers>
                    <DataTrigger Binding="{Binding ElementName=ModalWindow, Path=HasContent}"  Value="True">
                        <Setter TargetName="ModalWindow" Property="Visibility" Value="Visible"/>
                        <Setter TargetName="MainWPF" Property="IsEnabled" Value="False"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding ElementName=ModalWindow, Path=HasContent}" Value="False">
                        <Setter TargetName="ModalWindow" Property="Visibility" Value="Collapsed"/>
                        <Setter TargetName="MainWPF" Property="IsEnabled" Value="True"/>
                    </DataTrigger>
                </DataTemplate.Triggers>
            </DataTemplate>
        </ContentControl.ContentTemplate>
    </ContentControl>
</Window>

Ich habe die Regions im Code definiert und binde an sie. Der Modaledialog wird angezeigt, wenn FirstViewOfModalRegion eine View enthält(Ist eine Property die nur die erste View aus der ModalRegion "wraped") Bei mir enthält es ein ViewModel, welches einen Verweis auf die View enthält auf die ich dann binde.
Das einblenden und ausblenden und deaktivieren der anderen UserControls funktioniert über DataTriger.

Hoffe dir hilft der ModaleDialog(hab dazu auch irgendwo ein Artikel gelesen weiß aber nimmer wo und den Code leicht modifiziert).

Gruß Timo

manullino Themenstarter:in
371 Beiträge seit 2008
vor 14 Jahren

Hallo Omit,

vielen Dank fuer deinen Beitrag. Das hat mir schon sehr geholfen.

Vielleicht weiss ja jetzt noch jemand, wie ich das Problem mit den verschieden Region(Managern) loesen kann. (siehe Startbeitrag).

Das ist echt zum Verzweifeln...

Danke,
Manullino

143 Beiträge seit 2008
vor 14 Jahren

Die SingleActive Region macht, eigentlich nichts anderes als, die Activate Methode zu überschreiben.
Wenn du Activate aufrufst wird die CurrentActive Region durch Deactivate geschickt. Wenn du die Regions in XAML definierst wird glaub ich automatisch die richtige Region gewählt(Achtung ich lauf über glatt Eis, ich definiere alle meine Region im Code und binde zu ihnen).
Doch du kannst das ganze auch per Code erledigen. Ist aber nicht wirklich schön, da du überall wo du eine View der Region aktivierst diesen Code einfügen müsstest.


            object currentActiveView = MainRegion.ActiveViews.FirstOrDefault();

            if (currentActiveView != null && currentActiveView != view)
            {
                MainRegion.Deactivate(currentActiveView);
            }
            MainRegion.Activate(view);


Wenn du aber SingleActiveRegion auch auswählen kannst solltest du das so machen.

Gruß Timo

manullino Themenstarter:in
371 Beiträge seit 2008
vor 14 Jahren

Hallo Timo,

die MainRegion bekomme och odch ueber den RegionManager, oder?
In meinem Fall schaut das dann so aus.


IRegion mainRegion = _regionManager.Regions[ShellRegionNames.MainRegion];

Leider gibt es die Methode FirstOrDefault() nicht.
Das koennte an Silverlight liegen, oder?

 
object currentActiveView = mainRegion.ActiveViews.FirstOrDefault();

Guesse,
Manullino

143 Beiträge seit 2008
vor 14 Jahren

Du bekommst dort her nur eine schon bestehende Instanz. Diese wird aber wo anderst erzeugt. Also hast du an der stelle da keinen Einfluss mehr drauf.


            if (MainRegion.ActiveViews[0] != null && MainRegion.ActiveViews[0] != view)
            {
                MainRegion.Deactivate(MainRegion.ActiveViews[0]);
            }
            MainRegion.Activate(view); 

Gruß Timo

manullino Themenstarter:in
371 Beiträge seit 2008
vor 14 Jahren

Sorry, ich stehe gerade auf dem Schlauch.

Wie komme ich denn nun zu dem Object "MainRegion". 🤔

Meine Methode, die die View zur Region hinzufuegt/aktiviert schaut so aus:
(siehe auch Beitrag weiter oben)

        
// Method to show the Appointment Dashboard.
        private void OnGoToUserAccountDashboard(object parameter)
        {
            IUserAccountDashboardView dashboardView = _container.Resolve<IUserAccountDashboardViewModel>().View;
            IRegion mainRegion = _regionManager.Regions[ShellRegionNames.MainRegion];

            if (mainRegion.Views.Contains(dashboardView) == false)
            {
                mainRegion.Add(dashboardView);
            }
            //// Activate the Region
            mainRegion.Activate(dashboardView);
        }

Wenn ich das nun wie folgt aendere/hinzuguege, bekomme ich die ErrorMessage, dass ActiveViews keinen Index besitzt.
(Cannot apply indexing with [] to an expression of type 'Microsoft.Practices.Composite.Regions.IViewsCollection)


IRegion MainRegion = _regionManager.Regions[ShellRegionNames.MainRegion];
if (MainRegion.ActiveViews[0] != null && MainRegion.ActiveViews[0] != view)
            {
                MainRegion.Deactivate(MainRegion.ActiveViews[0]);
            }
            MainRegion.Activate(view);
/CSHARP]

Vielen Dank fuer Deine Bemuehungen,
Manullino
143 Beiträge seit 2008
vor 14 Jahren

Ok hätte ich vielleicht erwähnen sollen.

Musst noch casten zu Region.


        private void OnGoToUserAccountDashboard(object parameter)
        {
            IUserAccountDashboardView dashboardView = _container.Resolve<IUserAccountDashboardViewModel>().View;
            Region mainRegion = _regionManager.Regions[ShellRegionNames.MainRegion] as Region;´
            if (mainRegion != null)
            {
                        if (mainRegion.Views.Contains(dashboardView) == false)
                        {
                            mainRegion.Add(dashboardView);
                        }
                        //// Activate the Region

                        object currentActiveView = mainRegion.ActiveViews.FirstOrDefault();
                        if (currentActiveView != null && currentActiveView != view)
                        {
                            mainRegion.Deactivate(currentActiveView);
                        }
                        mainRegion.Activate(dashboardView);
            }
            else
            {
            //errorhandling
            }

        }

Mag nicht ganz sauber sein, aber bei mir tut es seine Dienste.

manullino Themenstarter:in
371 Beiträge seit 2008
vor 14 Jahren

Vielen Dank fuer deine Hilfe!

Ich hab die Regions nun mal direkt in den XAML Code gelegt.
Du hattest recht, da kuemmert sich der RegionManager dann automatisch um das deaktivieren.


        <Grid x:Name="TopGrid"  Grid.ColumnSpan="3">
                <ContentControl Regions:RegionManager.RegionName="{x:Static inf:HomeDashboardRegionNames.TopRegion}" />
            </Grid>
            <Grid x:Name="LeftGrid" Grid.Row="1">
                <ContentControl Regions:RegionManager.RegionName="{x:Static inf:HomeDashboardRegionNames.LeftRegion}"/>
            </Grid>
            <Grid x:Name="RightGrid" Grid.Column="2" Grid.Row="1" >
                <ContentControl Regions:RegionManager.RegionName="{x:Static inf:HomeDashboardRegionNames.RightRegion}"/>
            </Grid>
            <Grid x:Name="MainGrid" Grid.Column="1" Grid.Row="1">
                <ContentControl Regions:RegionManager.RegionName="{x:Static inf:HomeDashboardRegionNames.MiddleRegion}"/>
            </Grid>

Gruesse,
Manullino

143 Beiträge seit 2008
vor 14 Jahren

Freut mich das ich dir helfen konnte. Schöne Webseite. Bist du da im Urlaub oder Auslandsemester oder ausgewandert? Wenn ich fragen darf?

Gruß Timo

manullino Themenstarter:in
371 Beiträge seit 2008
vor 14 Jahren

Ich haette da noch eine Frage.

Wenn ich nun mit dem Debugger den RegionManager angschaue. Sind ALLE Regions (gruen und rot, siehe Startbeitrag) aufgelistet.

Ist es nicht besser, wenn ich fuer die Shell (gruen) einen RM habe und fuer jedes Dashboard (AppointmentDashboard, HomeDashboard, usw. (rot)) einen eigenen Region Manager habe?

Danke,
Manullino