web-dev-qa-db-ja.com

WPF MVVMパターンを使用してビューをナビゲートする

MVVMパターンを使用して最初のWPFを構築しています。このコミュニティの助けを借りて、モデル、最初のViewModelおよびビューを作成しました。次に、基本的なアプリケーションレイアウトインターフェイスを設計するアプリに複雑さを追加します。私のアイデアは、少なくとも2つの子ビューと1つのメインビューを用意し、それらを複数のXAMLで分離することです。

  • Main.XAML
  • Products.XAML
  • Clients.XAML

メインには、子ビュー(製品とクライアント)をロードするためのメニューとスペースがあります。 MVVMパターンに従って、ビュー間のすべてのナビゲーションロジックをViewModelに書き込む必要があります。したがって、4つのViewModelを使用することを考えます。

  • MainViewModel
  • 製品ビューモデル
  • ClientsViewModel
  • NavigationViewModel

したがって、NavigationViewModelには子ビューモデルのコレクションを含める必要がありますか?アクティブなビューモデルはそうですか?

だから私の質問は:

1)MVVMパターンを使用してメインビューにさまざまなビュー(製品、クライアント)を読み込むにはどうすればよいですか?

2)ナビゲーションviewModelを実装するにはどうすればよいですか?

3)開いているビューまたはアクティブなビューの最大数を制御するにはどうすればよいですか?

4)開いているビューを切り替えるにはどうすればよいですか?

私は多くの検索と読み取りを行ってきましたが、メインビュー内に複数のビューを読み込むWPFを使用したMVVMナビゲーションの簡単な動作例を見つけることができませんでした。その後の多く:

1)現在使用したくない外部ツールキットを使用します。

2)すべてのビューを作成するためのすべてのコードを単一のXAMLファイルに入れます。これは、80近くのビューを実装する必要があるため、良いアイデアとは思えません!

私は正しい道にいますか?特にいくつかのコードに関する助けをいただければ幸いです。

[〜#〜] update [〜#〜]

そこで、@ LordTakkeraのアドバイスに従ってテストプロジェクトを作成しますが、行き詰まります。これは私のソリューションがどのように見えるかです: Solution

私が作成します:

  • 2つのモデル(クライアントと製品)

  • 1つのMainWindowと2つのwpfユーザーコントロール(クライアントと製品)XAML。

  • 3つのViewModel(クライアント、製品、メインViewModel)

次に、各ビューのdataContextを対応するviewModelに設定します。その後、このようなContentPresenterを使用してMainWindowを作成し、それをビューモデルのプロパティにバインドします。

MainWindow.XAML

<Window x:Class="PruevaMVVMNavNew.MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="519" Width="890">    
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="150"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="80"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="20"/>
    </Grid.RowDefinitions>        
    <Border Grid.Column="0" Grid.ColumnSpan="2" Background="AntiqueWhite" ></Border>
    <Border Grid.Row="1" Grid.RowSpan="2" Background="AliceBlue"></Border>
    <Border Grid.Row="1" Grid.Column="1" Background="CadetBlue"></Border>                
    <ContentPresenter Grid.Row="1" Grid.Column="1" x:Name="ContentArea" Content="{Binding CurrentView}"/>        
    <StackPanel Margin="5" Grid.Column="0" Grid.Row="1">            
        <Button>Clients</Button>
        <Button>Products</Button>
    </StackPanel>
</Grid>

また、これはMainWindowのビューモデルです。

class Main_ViewModel : BaseViewModel
    {
        public Main_ViewModel()
        {
            CurrentView = new Clients();
        }

        private UserControl _currentView;
        public UserControl CurrentView
        {
            get
            {
                return _currentView;
            }
            set
            {
                if (value != _currentView)
                {
                    _currentView = value;
                    OnPropertyChanged("CurrentView");
                }
            }
        }

    }

したがって、これはデフォルトでクライアントに表示され、次のように表示されます(これはまさに正しいことです!):

Current state

したがって、左側のボタンを特定のviemodelに関連付けて、それらをMain viewModelのCurrentViewプロパティにバインドする方法が必要だと思います。どうやってやるの?

PDATE2

@LordTakkeraのアドバイスによると、メインビューモデルを次のように変更します。

class Main_ViewModel : BaseViewModel
    {
        public ICommand SwitchViewsCommand { get; private set; }

        public Main_ViewModel()
        {
            //CurrentView = new Clients();
            SwitchViewsCommand = new RelayCommand((parameter) => CurrentView = (UserControl)Activator.CreateInstance(parameter as Type));
        }

        private UserControl _currentView;
        public UserControl CurrentView
        {
            get
            {
                return _currentView;
            }
            set
            {
                if (value != _currentView)
                {
                    _currentView = value;
                    OnPropertyChanged("CurrentView");
                }
            }
        }
    }

DelegateCommandの代わりにRelayCommandを使用しますが、同じように機能すると思います。ボタンを押して型パラメーター文字列をOKにするとコマンドが実行されますが、このエラーが表示されます:

Error

翻訳:値をnullにすることはできません。パラメータ名:タイプ。提案は新しいキーワードを使用してオブジェクトインスタンスを作成します新しいキーワードを配置する場所がわかりません。 CommandParameterを試してみましたが、うまくいきません。何か案が?ありがとう

更新

ここで受け取ったすべてのアドバイスとヘルプ、および多くの作業の後に、最終的なナビゲーションメニューとアプリケーションインターフェイスのベースを示します。

Capture 1Capture 2

42
ericpap

別の「ナビゲーション」ビューモデルが必要かどうかはわかりませんが、メインに簡単に入れることができます。どちらにしても:

「子」ビューを分離するには、「メイン」ビューで簡単なContentPresenterを使用します。

<ContentPresenter Content="{Binding CurrentView}"/>

バッキングプロパティを実装する最も簡単な方法は、UserControlにすることです。ただし、これはMVVMに違反すると主張する人もいます(ViewModelは "View"クラスに依存しているため)。オブジェクトにすることはできますが、タイプセーフは失われます。この場合、各ビューはUserControlになります。

それらを切り替えるには、何らかの選択コントロールが必要になります。ラジオボタンを使用してこれを実行したことがあります。次のようにバインドします。

<RadioButton Content="View 1" IsChecked="{Binding Path=CurrentView, Converter={StaticResource InstanceEqualsConverter}, ConverterParameter={x:Type views:View1}"/>

コンバーターは非常に単純です。「Convert」では、現在のコントロールがパラメーターのタイプであるかどうかを確認し、「ConvertBack」ではパラメーターの新しいインスタンスを返します。

public class InstanceEqualsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return (parameter as Type).IsInstanceOfType(value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return (bool)value ? Activator.CreateInstance(parameter as Type) : Binding.DoNothing;
    }
}

コンボボックスまたは他の選択コントロールへのバインドは、同様のパターンに従います。

もちろん、DataTemplates(セレクター、残念ながら私が前にやったことではない)を使用し、マージされた辞書を使用してリソースにロードすることもできます(個別のXAMLを許可します)。私は個人的にユーザー制御ルートを好むので、あなたに最適なものを選んでください!

このアプローチは「一度に1つのビュー」です。複数のビューに変換するのは比較的簡単です(UserControlはユーザーコントロールのコレクションになり、コンバータで.Containsを使用するなど)。

ボタンでこれを行うには、コマンドを使用し、CommandParameterを利用します。

ボタンXAMLは次のようになります。

<Button ... Command={Binding SwitchViewsCommand} CommandParameter={x:Type local:ClientsView}/>

次に、コンバーターからアクティベーターコードを実行するデリゲートコマンド(チュートリアル ここ )があります。

public ICommand SwitchViewsCommand {get; private set;}

public MainViewModel()
{
    SwitchViewsCommand = new DelegateCommand((parameter) => CurrentView = Activator.CreateInstance(parameter as Type));
}

これは私の頭上ではありませんが、かなり近いはずです。それがどうなるか教えてください!

さらに情報を提供するかどうかを教えてください!

更新:

あなたの懸念に答えるには:

  1. はい、ボタンを押すたびに、ビューの新しいインスタンスが作成されます。これを簡単に修正するには、Dictionary<Type, UserControl>事前に作成されたビューとそのインデックスがあります。その点については、Dictonary<String, UserControl>およびコンバーターパラメーターとして単純な文字列を使用します。欠点は、ViewModelが表示できる種類のビューと密に結合されることです(前述のDictionaryを設定する必要があるため)。

  2. クラスは、他の誰もそれへの参照を保持していない限り、破棄されます(登録されたイベントハンドラを考えてください)。

  3. 指摘したとおり、一度に作成されるビューは1つだけなので、メモリについて心配する必要はありません。もちろん、コンストラクタを呼び出すことはできますが、それはそれほど高価ではありません。特に、十分なCPU時間を確保する傾向がある現代のコンピューターではそうです。いつものように、パフォーマンスの質問に対する答えは「ベンチマーク」です。これは、意図した展開ターゲットとソース全体にアクセスして、実際に最高のパフォーマンスを発揮するものを確認できるからです。

20
BradleyDotNET

ナビゲーションは既に実装されているため、IMHOで最適な選択はMVVMフレームワーク(PRISM、MVVM Light、Chinchなど)を使用することです。独自のナビゲーションを作成する場合は、DataTemplateを試してください。

0
Alex Erygin