WPF Caliburn.Micro и TabControl с проблемой UserControls

Я почти уверен, что на это был дан ответ, но я не могу найти его для жизни.

Я пытаюсь использовать TabControl для переключения между UserControls (каждая вкладка отличается, поэтому не используя элементы)

Здесь разбивка: У меня есть мое основное представление и 3 пользовательских элемента управления. Mainview имеет элемент управления вкладкой - каждая вкладка должна отображать другой пользовательский элемент управления.

Я мог бы просто установить параметр tabcontrol для usercontrol, используя Но тогда это не связано с viewmodel, только вид.

Итак, я использую проводник в своей виртуальной машине и ActivateItem. Здесь, где он начинает становиться странным/расстраивающим. Приложение запускается с выбранным Tab0, но Tab2 (последняя вкладка). Нажмите на любую другую вкладку, загрузите правильную ViewModel для этой вкладки. Нажмите обратно на Tab0, загрузите там правильный контент.

Как мне это остановить? Кроме того, мне бы очень хотелось, чтобы при переключении вкладок снова не было повторно инициализировано viewmodel, очистка полей, которые уже были введены.

В любом случае, здесь некоторые из моих источников, я собираюсь просто отбросить это здесь и работать над чем-то еще, прежде чем я сломаю мышь.

Вид:

<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);
    }
}

Ответ 1

Могу ли я предложить немного другой маршрут?

Это то, что я успешно выполнял в сценариях основных деталей. Скажем, у вас есть коллекция моделей детского просмотра. Я подготовлю интерфейс маркера для всех этих элементов, конечно, вы можете добавить свойства/методы, которые вам подходят, если есть такие методы, которые охватывают все модели детского вида:

public interface IMainScreenTabItem : IScreen
{
}

Вы можете быть уверены, что хотите, чтобы все ваши дочерние модели были Screen (или, в случае вложенных сценариев, Conductor s). Это заставляет их иметь полный цикл инициализации/активации/деактивации.

Затем модели child view:

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 является виртуальным свойством, и большой нет-нет для вызова виртуальных методов в конструкторе класса, который не запечатан.

Затем вы можете добавить соответствующие представления и установить свой контейнер IoC для выбора регистраций - вам необходимо зарегистрировать все модели просмотра для детей как классы, реализующие IMainScreenTabItem, а затем:

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

Где MainView.xaml справедливо:

<TabControl Name="Items"/>

И это просто работает. Это также очень приятное и удобное решение, если модели просмотра вашего ребенка принимают несколько зависимостей (например, доступ к базе данных, регистратор, механизм проверки и т.д.), Теперь вы можете использовать IoC для всего тяжелого лифтинга, а не для его создания вручную.

Одна вещь здесь: вкладки будут помещены в том же порядке, в который вводятся классы. Если вы хотите получить контроль над заказом, вы можете заказать их в конструкторе MainViewModel путем передачи пользовательского IComparer<IMainScreenTabItem> или добавления некоторого свойства, которое вы можете OrderBy, или выберите интерфейс IMainScreenTabItem. Выбранный по умолчанию элемент будет первым в списке Items.

Другой вариант - сделать MainViewModel тремя параметрами:

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) и активируются, когда они перемещаются (OnActivate). Параметр bool в OnDeactivate указывает, просто ли отключена модель просмотра или полностью закрыта (например, когда вы закрываете диалоговое окно и перемещаетесь). Если вы полностью закрываете модель представления, она будет повторно инициализирована в следующий раз, когда она будет показана.

Это означает, что любые связанные данные будут сохраняться между вызовами OnActivate, и вам нужно будет явно очистить его в OnDeactivate. Что еще, если вы сохраните сильную ссылку на модели просмотра вашего ребенка, то даже после вызова OnDeactivate(true) данные будут по-прежнему присутствовать при следующей инициализации - что, поскольку модели ввода-вывода с IoC создаются один раз (если вы не введете factory в форме 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);
    }
}

Обратите внимание на регистрацию viewModels в Configure.