Laden...

MVVM : Öffnen einer Seite durch Model

Erstellt von Ahrimaan vor 12 Jahren Letzter Beitrag vor 12 Jahren 3.306 Views
A
Ahrimaan Themenstarter:in
350 Beiträge seit 2010
vor 12 Jahren
MVVM : Öffnen einer Seite durch Model

Hallo zusammen,
In meinem TreeView habe ich eine Collection von RootNodes welche Subnodes enthalten.
Diese SubNodes haben eine Property Extended Data.
Nun meine Frage : Extended Data soll bei Klick gefüllt werden, bedeutet es wird ein Dialog geöffnet.
Irgendwie habe ich Bauchschmerzen, aus dem Model heraus einen Dialog zu öffnen, das passt ja irgendwie nicht zum MVVM Pattern (denke ich)
Aber : Wie kann ich es sonst lösen ?

Jmd eine Idee ?
Grüße

3.430 Beiträge seit 2007
vor 12 Jahren

Hallo,

aus dem Model heraus sollte man wirklich keine Dialoge öffnen.
Das Model ist nur für die Daten und sollte nicht für andere Dinge "missbraucht" werden.

Ich weiß jetzt nicht genau wie den Programm aufgebaut ist aber wahrscheinlich ist es am Besten wenn du das über ein ViewModel realisierst welches du dazwischen schaltest.

Gruß
Michael

A
Ahrimaan Themenstarter:in
350 Beiträge seit 2010
vor 12 Jahren

Hi Michl,

<Grid DataContext="{Binding Source={StaticResource vm}}" Width="262">
        <Grid.RowDefinitions>
            <RowDefinition Height="196*" />
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>
        <TreeView ItemsSource="{Binding Items}" Margin="-9,0,-7,0">
            <TreeView.ItemContainerStyle>
                <Style TargetType="{x:Type TreeViewItem}" >
                    <Setter Property="IsSelected" Value="{Binding Path=IsSelected}"/>

                    <Style.Triggers>
                        <Trigger Property="HasItems" Value="true">
                            <Setter Property="Focusable" Value="False"/>
                        </Trigger>                    
                    </Style.Triggers>
                </Style>
            </TreeView.ItemContainerStyle>
            <TreeView.Resources>
                <HierarchicalDataTemplate DataType="{x:Type model:RootNode}" ItemsSource="{Binding Path=SubNodes}">
                    <Grid>
                        <TextBlock Text="{Binding Text}" />
                    </Grid>
                </HierarchicalDataTemplate>
                <HierarchicalDataTemplate DataType="{x:Type model:SubNodeItemWithCheckbox}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="16"/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <CheckBox Grid.Column="0" IsChecked="{Binding IsSelected, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}"/>
                        <TextBlock Text="{Binding Text}" Grid.Column="1"/>
                    </Grid>
                </HierarchicalDataTemplate>
                <HierarchicalDataTemplate DataType="{x:Type model:SubNode}">
                        <TextBlock Text="{Binding Text}"/>
                </HierarchicalDataTemplate>
            </TreeView.Resources>
        </TreeView>


        <Button Grid.Row="1" Width="30" Command="{Binding OkCommand}">
            <Image Source="/Images/Sign001.ico"/>
        </Button>
    </Grid>

Und die Models so :

public class RootNode
    {
        public string Text { get; set; }
        public ObservableCollection<INode> SubNodes { get; set; }

        private bool _isEnabled;
        public bool IsEnabled { get { return _isEnabled; } set { _isEnabled = value; } }

        private bool _isSelected;
        public bool IsSelected
        {
            get { return _isSelected; } 
            set { _isSelected = value; }
        }


        private ExtendedData _extData;
        public ExtendedData ExtData { get { return _extData; } set { _extData = value; } }

    }

Subnode :

public class SubNode :INode
    {
        public SubNode(RootNode parent)
        {
            _parent = parent;
        } 

        private RootNode _parent;

        private bool _isEnabled;
        public bool IsEnabled
        {
            get { return _isEnabled; }
            set { _isEnabled = value;OnPropertyChanged(() =>  IsEnabled); }
        }

        private bool _isSelected;
        public bool IsSelected
        {
            get { return _isSelected; }
            set { _isSelected = value;OnPropertyChanged(() => IsSelected); }
        }

        private string _text;
        public string Text
        {
            get { return _text; }
            set { _text = value;OnPropertyChanged(() => Text); }
        }

        public RootNode Parent
        {
            get { return _parent; }
        }

        private ExtendedData _extData;
        public ExtendedData ExtData
        {
            get { return _extData; }
            set { _extData = value;OnPropertyChanged(() => ExtData); }
        }
    }

Wie sollte ich es denn deiner Meinung nach Verwurschteln ? Es geht übrigens um die Property ExtendedData

ViewModel :

  public class MainWindowModel:BaseViewModel
    {
        public MainWindowModel()
        {
            Items = new ObservableCollection<IRootNode>(DatabaseLoader.GetItems()); 

        }

        private string _windowTitle;
        public string WindowTitle {
            get { return _windowTitle; } 
            set { _windowTitle = value;OnPropertyChanged(() => WindowTitle); } 
        }

        private ObservableCollection<IRootNode> _items;
        public ObservableCollection<IRootNode> Items
        {
            get { return _items ?? (_items = new ObservableCollection<IRootNode>()); }
            private set { _items = value; OnPropertyChanged(() => Items); }
        }

        private ICommand _okCommand;
        public ICommand OkCommand
        {
            get { return _okCommand ?? new DelegateCommand(ButtonOkCommandExecuted); }
        }


        private readonly DataPersistance _persistance;
        private void ButtonOkCommandExecuted()
        {
            SuccesMethod(_persistance.SaveData(Items));
            
        }

        private void SuccesMethod(int succes)
        {
            if (succes == 1)
                Environment.Exit(1);
            if(succes == -1)
                MessageBox.Show("Es ist ein Fehler beim Speichern aufgetreten !", "Fehler", MessageBoxButton.OK,
                            MessageBoxImage.Error);
            if(succes == 0)
                MessageBox.Show("Es muss mindestens eine Klassifizuerung ausgewählt werden !", "Fehler", MessageBoxButton.OK,
                            MessageBoxImage.Error);
        }

    }

Grüße

F
10.010 Beiträge seit 2004
vor 12 Jahren

Wenn Du wirklich MVVM machst ist doch die Logik des Views in dem ViewModel, das Du uns nicht gezeigt hast.

A
Ahrimaan Themenstarter:in
350 Beiträge seit 2010
vor 12 Jahren

Ist im Beitrag editiert, sorry

6.862 Beiträge seit 2003
vor 12 Jahren

Hallo,

da sind noch so einige Dinge nicht wirklich MVVM-like.

Du hast in deinen Templates oft Typangaben wie model:RootNode oder model:SubNode. Das sieht stark danach aus, als wenn du direkt die Modeltypen in die View gibst - das darf man im MVVM natürlich nicht. Dafür muss man entsprechende ViewModels machen, die diese kapseln.

Was das Öffnen eines Dialogs angeht: Das Model ist dafür natürlich die falsche Stelle. Das entsprechende ViewModel, was ja dann auch das Command für den Click haben sollte welches die Daten befüllen soll, kann das aber schon. Aber natürlich nicht direkt, denn UI Code gehört nicht ins ViewModel. Dafür benutzt man in der Regel nen Service Locator. Die gängigen MVVM Frameworks implementieren das schon.

Sowas

        private void SuccesMethod(int succes)
        {
            if (succes == 1)
                Environment.Exit(1);
            if(succes == -1)
                MessageBox.Show("Es ist ein Fehler beim Speichern aufgetreten !", "Fehler", MessageBoxButton.OK,
                            MessageBoxImage.Error);
            if(succes == 0)
                MessageBox.Show("Es muss mindestens eine Klassifizuerung ausgewählt werden !", "Fehler", MessageBoxButton.OK,
                            MessageBoxImage.Error);
        }


geht natürlich gar nicht in nem ViewModel. Da gehört kein UI Code rein. Das ViewModel sollte nicht mal wissen das es ne GUI hat wo es angezeigt wird.

Baka wa shinanakya naoranai.

Mein XING Profil.

A
Ahrimaan Themenstarter:in
350 Beiträge seit 2010
vor 12 Jahren

Hi Talla,
ok danke für die Info.
Ich habe die Infos mir aus dem MSDN Forum geben lassen sowie von Stackoverflow. Auf meine Nachfrage hin, wurde gesagt ja das ist in MVVM erlaubt also kein Problem.

Das heißt für mich nun, ich mache mir eine Area , wo ich je nach Typ meines Model ein anderes ViewModel lade ?
Oder wie würdest du das lösen ?
JETZT stehe ich nämlich vollkommen auf dem Schlauch....

Grüße

I
8 Beiträge seit 2010
vor 12 Jahren

Nope:

Deine Model Klassen sind mit den Viewmodelklassen stark gebunden, ein Viewmodel ist ein Wrapper um ein (oder mehrere) Model Klassen.

Ein Viewmodel hingegen ist NICHT stark an eine View gebunden, sondern nur lose über Properties welche das VM bereitstellt, und die View dann über Databinding losen Zugriff hat.

Folgendes wäre eine Methode in einem ViewModel, welches MVMV schön demonstriert:


void ShowCustomerDetail(Customer cust){
	
	var vm = new CustomerViewModel(cust);
	var workbenchServ = ServiceLocator.Resolve<IWorkbenchService>();
	workbenchServ.Show(vm);
}

Was dir hier auffallen sollte ist, dass ein VM "gezeigt wird". Mit welcher View ein VM gezeigt wird, abstrahiere -zumindest ich - immer mit einem WorkbenchService. Man hat daher beim Appstart oft Mappings, wo die ViewModel-Klassen zu Views gemappt werden.

Ein Auszug aus meinem Code:


/// <summary>
        /// Configure Services
        /// </summary>
        void ConfigureServices() {

            // config the ViewModel to View mappings for Dialoges  / WorkbenchServices
            var mappingService = _serviceLocator.Resolve<IWorkBenchService>().MappingService;
            mappingService.RegisterMapping(typeof(AboutViewModel), typeof(AboutControlView));
            mappingService.RegisterMapping(typeof(AllPluginsViewModel), typeof(PluginOverviewControl));

            mappingService.RegisterMapping(typeof(SalesmanMapViewModel), typeof(SalesmanMapView));
            mappingService.RegisterMapping(typeof(AllCustomersViewModel), typeof(AllCustomersView));
            mappingService.RegisterMapping(typeof(CustomerViewModel), typeof(CustomerView));


            mappingService.RegisterMapping(typeof(DocumentViewerViewModel), typeof(DocumentViewerView));
       // usw...
}

Für einzelne Items in einem Fenster nutzt man dann kein Mapping mehr sondern DataTemplates, die je nach ViewModel-Typ gewählt werden.

A
Ahrimaan Themenstarter:in
350 Beiträge seit 2010
vor 12 Jahren

Hallo IsNull,
das ganze muss doch aber auch ohne MEF MAF oder einen IOC Container möglich sein oder ?

So wie ich das verstanden habe muss ich folgendes machen :
Ich habe eine MainView, mit meinetwegen mit einem ContentPresenter.
Dieser ContentPresenter ist an eine Property im MainViewModel gebunden.
Diese Property ist vom Typ ITreeView.

Jetzt muss ich ja quasi nur 3 verschiedene Templates für ein TreeView bauen und es je nach ViewModelTyp auswählen oder ?

Was ich aber immer noch nicht verstehe ich ich eien SubNode so binde, dass eine weitere View geöffnet wird.....

Grüße

A
Ahrimaan Themenstarter:in
350 Beiträge seit 2010
vor 12 Jahren

So , mal gucken ob ich es zum Teil verstanden habe :
Mein MainWindowView :

<Window x:Class="MK.AgentWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:MK.AgentWpf.ViewModel"
        Title="Klassifizierung" Height="606" Width="283" 
        Icon="/MK.AgentWpf;component/Images/App064.ico" Topmost="True" 
        ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
    <Window.DataContext>
        <local:MainWindowModel/>
    </Window.DataContext>
    <Window.Resources>
        <ResourceDictionary Source="/MainWindowResources.xaml"/>
    </Window.Resources>
    <Grid>
        <ContentControl Content="{Binding Path=TreeViewModel,Mode=TwoWay}"/>
    </Grid>
</Window>

Mein MainWindowViewModel :

        public MainWindowModel()
        {
            SetTypeOfModel();
        }

        private ITreeViewViewModel _treeViewModel;
        public ITreeViewViewModel TreeViewModel
        {
            get { return _treeViewModel;}
            set { _treeViewModel = value;OnPropertyChanged(() => TreeViewModel); }
        }

        private void SetTypeOfModel()
        {
           if(Util.AppConfig.Multiklass.Muliclass)
               TreeViewModel = new TreeViewViewModel(DatabaseLoader.GetItems());
        }

Eines meiner TReeViewControls :

<UserControl x:Class="MK.AgentWpf.View.BasicTreeWindow"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="606" Width="283"
             >
  <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="551*" />
            <RowDefinition Height="55*"/>
        </Grid.RowDefinitions>
            <TreeView ItemsSource="{Binding Items}">
            <TreeView.ItemContainerStyle>

                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsSelected" Value="{Binding Path=IsSelected}"/>
                    <Style.Triggers>
                        
                        <Trigger Property="HasItems" Value="true">
                            <Setter Property="Focusable" Value="False"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </TreeView.ItemContainerStyle>
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding SubNodes}">
                    <TextBlock Text="{Binding Text}"/>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
        <Button Command="{Binding SaveCommand}" Grid.Row="1" Width="35" Height="35">
            <Image Source="/Images/Sign001.ico" />
        </Button>
        
    </Grid>
</UserControl>

Und das passende ResourceDictionary :

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:vm="clr-namespace:MK.AgentWpf.ViewModel"
                    xmlns:vw="clr-namespace:MK.AgentWpf.View" >
    
    <DataTemplate DataType="{x:Type vm:TreeViewViewModel}">
        <vw:BasicTreeWindow/>
    </DataTemplate>
</ResourceDictionary>

Es wird nun vom MainViewModel geprüft , welcher Typ ITreeViewViewModel gebraucht wird und lädt diesen. Durch die Verknüpfung im ResourceDirectory weiß die View zu welchem TreeViewViewModel es gehört und wird geladen.
Fertig.

Nur ist mir das mit meinen ExtendedData propertys immer noch schleierhaft , aber die Nacht is ja noch jung 😉

EDIT: Ich sollte doch nur mit WinForms arebiten, das ganze wird mir einfach zu hoch: Jetzt sagt er mir " BindingExpression path error: 'SubNodes' property not found"
Ich kriege echt die Krise....

I
8 Beiträge seit 2010
vor 12 Jahren

Hi,

das ganze muss doch aber auch ohne MEF MAF oder einen IOC Container möglich sein oder ?

Ja das ist es allemal. Der Vorteil dieser Abstraktion ist, dass du als Fenster-Backend nutzen kannst was du willst: Du kannst direkt die WPF Fenster nutzen, du kannst aber auch einen Dockingmanager - an der Stelle führe ich mal AvalonDock auf - oder irgend eine Eigenkreation nutzen. Das ganze wird flexibler, und der Abstraktionsaufwand hällt sich in überschaubaren Grenzen.

Wegen dem Tree-View:
Wieso erfindest du das Rad neu? Google einfach nach TreeView + MVMV und schon kriegst du Beispielimplementationen. Das hier schaut ganz brauchbar aus: Simplifying the WPF TreeView by Using the ViewModel Pattern
Damit hast du eine saubere Implementation als Grundlage, und du kannst das auf dein Problem 1:1 übertragen.

Bleib mir mit Winforms bloss auf Distanz 😄

A
Ahrimaan Themenstarter:in
350 Beiträge seit 2010
vor 12 Jahren

Hi,
ok super, dann kann ich ja nun MVVM konform arbeiten 😉
Das mit dem TreeView ist ein guter Tipp, den schau ich mir mal an.
Bin echt nicht drauf gekommen, die ganzen Items als ViewModels zu nutzen.

Grüße

A
Ahrimaan Themenstarter:in
350 Beiträge seit 2010
vor 12 Jahren

Hi IsNull,
nochmal vielen Dank für deine hilfe.

Eine letzte Frage bleibt mir aber :
Wie gehe ich am besten vor um neue Fenster zu öffnen ?
Ich habe eine Lösung gesehen mit einem WindowManager , welcher eine View Instanziert.
Das ist ja nicht MVVM Konform.

Wie kann ich also am klügsten ein Fenster öffnen ?

Grüße