c# - WPF Caliburn.MicroとTabControl with UserControlsの問題



mvvm (1)

私はこれがどこかで答えられたと確信しています、しかし私は私の人生のためにそれを見つけることができないようです。

TabControlを使用してUserControlを切り替えようとしています(各タブは異なるので、Itemsは使用しません)。

これが内訳です。私のメインビューと3つのユーザーコントロールがあります。 Mainviewにはタブコントロールがあります - 各タブには異なるユーザーコントロールが表示されます。

タブコントロールコンテクストをユーザーコントロールに簡単に設定することができますが、ビューモデルだけではなくビューのみに限定されます。

だから私は私のVM、そしてActivateItemでConductorを使用しています。 ここでそれは奇妙な/欲求不満になり始めるところです。 アプリケーションはTab0が選択された状態で開始しますが、Tab2(最後のタブ)の内容です。 他のタブをクリックして、そのタブの正しいViewModelをロードします。 Tab0に戻ってクリックし、そこにも正しいコンテンツをロードします。

これを止めるにはどうすればよいですか。 また、タブを切り替えてもビューモデルが再初期化されず、すでに入力されているフィールドが消去されないようにしたいと思います。

とにかく、ここに私の情報源の一部があります、私はこれをここに落として、私が私のマウスを壊す前に何か他のものに取り組むつもりです。

見る:

<TabControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row ="1">
        <TabItem Header="PC Information">
            <Grid>
                <ContentControl x:Name="LoadRemoteInfo" cal:View.Model="{Binding ActiveItem}"/>
            </Grid>
        </TabItem>
        <TabItem Header="Remote Tools">
            <Grid>
                <ContentControl x:Name="LoadRemoteTools" cal:View.Model="{Binding ActiveItem}"/>
            </Grid>
        </TabItem>
        <TabItem Header="CHRemote">
            <Grid>
                <ContentControl x:Name="LoadCHRemote" cal:View.Model="{Binding ActiveItem}"/>
            </Grid>
        </TabItem>

    </TabControl>

そしてViewModel:

class MainViewModel : Conductor<object>
{
    RemoteInfoViewModel remoteInfo = new RemoteInfoViewModel();
    RemoteToolsViewModel remoteTools = new RemoteToolsViewModel();
    CHRemoteViewModel chRemote = new CHRemoteViewModel();

    public MainViewModel()
    {
        ActivateItem(remoteInfo);
    }

    public void LoadRemoteInfo()
    {
        ActivateItem(remoteInfo);
    }

    public void LoadRemoteTools()
    {
        ActivateItem(remoteTools);
    }

    public void LoadCHRemote()
    {
        ActivateItem(chRemote);
    }
}

少し違うルートを提案してもいいですか。

これは私がマスターディテールのシナリオでうまくやってきたことです。 子ビューモデルのコレクションがあるとしましょう。 これらすべての項目に対してマーカーインタフェースを用意します。もちろん、すべての子ビューモデルにまたがるようなメソッドがある場合は、適切と思われるプロパティ/メソッドを追加できます。

public interface IMainScreenTabItem : IScreen
{
}

すべての子モデルをScreen (または、入れ子になったシナリオの場合はConductor )にしたいことは間違いありません。 それは彼らに利用可能な完全な初期化/活性化/非活性化サイクルを持たせる。

次に、子ビューは次のモデルを作成します。

public sealed class ChRemoteViewModel : Screen, IMainScreenTabItem
{
    public ChRemoteViewModel()
    {
        DisplayName = "CH Remote";
    }
}

public sealed class PcInfoViewModel : Screen, IMainScreenTabItem
{
    public PcInfoViewModel()
    {
        DisplayName = "PC Info";
    }
}

public sealed class RemoteToolsViewModel : Screen, IMainScreenTabItem
{
    public RemoteToolsViewModel()
    {
        DisplayName = "Remote Tools";
    }
}

DisplayNameはヘッダーテキストとして表示されます。 DisplayNameは仮想プロパティであるため、これらのクラスをシールすることをおDisplayNameします。シールされていないクラスのコンストラクタで仮想メソッドを呼び出すのは非常にDisplayNameです。

それから、対応するビューを追加し、選択登録のIoCコンテナーを設定することができます - あなたはIMainScreenTabItemを実装するクラスとしてあなたのすべての子ビューモデルを登録しなければなりません:そして

public class MainViewModel : Conductor<IMainScreenTabItem>.Collection.OneActive
{
    public MainViewModel(IEnumerable<IMainScreenTabItem> tabs)
    {
        Items.AddRange(tabs);
    }
}

MainView.xamlは次のとおりです。

<TabControl Name="Items"/>

そしてそれはうまくいきます。 また、子ビューモデルが複数の依存関係(データベースアクセス、ロガー、検証メカニズムなど)を使用している場合は、手でインスタンス化するのではなく、IoCにすべての面倒な作業を行わせることもできます。

ただし、ここで1つだけです。タブは、クラスが挿入されたのと同じ順序で配置されます。 順序を制御したい場合は、カスタムのIComparer<IMainScreenTabItem>を渡すか、 OrderByまたはIMainScreenTabItemインターフェイスに選択できるプロパティを追加して、 IMainScreenTabItemできます。 デフォルトで選択されている項目は、 Itemsリストの最初の項目になります。

他のオプションはMainViewModelに3つのパラメータを取らせることです:

public MainViewModel(ChRemoteViewModel chRemoteViewModel, PcInfoViewModel pcInfo, RemoteToolsViewModel remoteTools)
{
    // Add the view models above to the `Items` collection in any order you see fit
}

2〜3個以上の子ビューモデルがある場合(さらに簡単にそれ以上取得できる場合もあります)、それは面倒なことになります。

「清算」部分について。 IoCによって作成されたビューモデルは通常のライフサイクルに合います:それらはせいぜい一度だけ初期化され( OnInitialize )、次にOnDeactivate(bool)から離れてナビゲートされるたびに非アクティブ化されOnDeactivate(bool)そしてナビゲートされるときにアクティブ化されます( OnActivate )。 OnDeactivateboolパラメータは、ビューモデルが非アクティブ化されたばかりか、完全に '閉じられた'か(たとえば、ダイアログウィンドウを閉じて別の場所に移動したとき)を示します。 ビューモデルを完全に閉じると、次に表示されるときに再初期化されます。

つまり、 OnActivate呼び出しの間にバインドされたデータは保持され、 OnDeactivate明示的に消去する必要があります。 さらに、自分の子ビューモデルへの強い参照を保持している場合、 OnDeactivate(true)を呼び出した後でも、データは次回の初期化時にまだ存在します - IoCインジェクションビューモデルは一度作成されるためです。 Func<YourViewModel> )の形式で機能してから、必要に応じて初期化/有効化/無効化します。

編集

ブートストラップについては、どのような種類のIoCコンテナを使用しているのかよくわかりません。 私のサンプルではSimpleInjector使っていますが、Autofacなどでも同じことが簡単にできます。

public class AppBootstrapper : Bootstrapper<MainViewModel>
{
    private Container container;

    /// <summary>
    /// Override to configure the framework and setup your IoC container.
    /// </summary>
    protected override void Configure()
    {
        container = new Container();
        container.Register<IWindowManager, WindowManager>();
        container.Register<IEventAggregator, EventAggregator>();
        var viewModels =
            Assembly.GetExecutingAssembly()
                .DefinedTypes.Where(x => x.GetInterface(typeof(IMainScreenTabItem).Name) != null && !x.IsAbstract && x.IsClass);
        container.RegisterAll(typeof(IMainScreenTabItem), viewModels);
        container.Verify();
    }

    /// <summary>
    /// Override this to provide an IoC specific implementation.
    /// </summary>
    /// <param name="service">The service to locate.</param><param name="key">The key to locate.</param>
    /// <returns>
    /// The located service.
    /// </returns>
    protected override object GetInstance(Type service, string key)
    {
        if (service == null)
        {
            var typeName = Assembly.GetExecutingAssembly().DefinedTypes.Where(x => x.Name.Contains(key)).Select(x => x.AssemblyQualifiedName).Single();

            service = Type.GetType(typeName);
        }
        return container.GetInstance(service);
    }

    protected override IEnumerable<object> GetAllInstances(Type service)
    {
        return container.GetAllInstances(service);
    }

    protected override void BuildUp(object instance)
    {
        container.InjectProperties(instance);
    }
}

ConfigureviewModels登録に注意してください。





caliburn.micro