Как запускать метод каждый раз, когда выбирается TabItem, в приложении MVVM с использованием Prism

Я пытался реализовать это какое-то время и до сих пор не мог этого сделать, несмотря на то, что мне казалось, что это должно быть что-то легкое.

Сложность связана с тем, что я реализовал приложение WPF с использованием шаблона MVVM. Это моя первая попытка использовать как шаблон, так и фреймворк, поэтому почти гарантировано, что я допустил ошибки, пытаясь следовать рекомендациям MVVM.

Моя реализация

У меня есть три представления с их соответствующими моделями представления (подключенными с помощью метода Prism AutoWireViewModel). MainView имеет TabControl с двумя TabItem, каждый из которых содержит контейнер Frame с Source, установленным на один из двух других View. Следующий код представляет собой отрывок из MainView:

<TabControl Grid.Row = "1" Grid.Column = "1">
    <TabItem Header = "Test">
        <!--TestView-->
        <Frame Source = "View1.xaml"/>
    </TabItem>
    <TabItem Header = "Results">
        <!--ResultsView-->
        <Frame Source = "View2.xaml"/>
    </TabItem>
</TabControl>

Моя проблема

Каждый раз, когда кто-то переходит на конкретный TabItem, я хотел бы запустить метод, который обновляет один из элементов управления WPF, включенных в этот View. Этот метод уже реализован и привязан к Button, но в идеале кнопки не требуется, я бы хотел иметь какой-нибудь Event, чтобы это произошло.

Я заранее ценю всю помощь.

Не могли бы вы выложить xaml? а один из ViewModels?

3xGuy 30.04.2018 16:01

Где определяется метод? В представлении или в модели представления?

mm8 30.04.2018 16:05

@ 3xGuy Я добавил код xaml, но код ViewModel довольно длинный. Что именно вы хотите?

r_laezza 30.04.2018 16:12

спасибо, я разберусь, дайте мне несколько.

3xGuy 30.04.2018 16:13

@ mm8 Метод определен во ViewModel

r_laezza 30.04.2018 16:14

@ mm8 Да, я привязал к свойству ButtonCommand объект DelegateCommand (Prism), определенный в моей ViewModel.

r_laezza 30.04.2018 16:19

Tabcontrol - это тип селектора. msdn.microsoft.com/en-us/library/… Когда вы выбираете элемент табуляции, вы делаете элемент выбранным. Вы можете привязать selecteditem к свойству в вашей модели просмотра и действовать для этого в сеттере. Вы также можете выполнить привязку и шаблон для создания элементов табуляции.

Andy 30.04.2018 16:23

Видя, что у вас есть фреймы, я думаю, это потому, что вы используете страницы. Редко хорошая идея, обычно лучше использовать элементы управления пользователем. Тогда вы можете просто поместить пользовательский элемент управления прямо в tabitem.

Andy 30.04.2018 16:32

@ Энди, я могу легко изменить свои просмотры на UserControl. Не могли бы вы пояснить, почему использовать Page - плохая идея?

r_laezza 02.05.2018 08:58

Contentcontrol и usercontrols - это обычный способ работы, потому что они легче и гибче. Страницы были изобретены на заре WPF, когда они предназначались для использования в браузере, а также на рабочем столе или в браузере. Редко хороший план. Они ограничивают в том, что вы можете поместить их только в рамки. Рамы представляют собой относительно высокий контейнер с накладными расходами из-за своего журнала. Если вы не собираетесь использовать это средство ведения журнала, тогда они просто накладные расходы по сравнению с контролем содержимого. Рамки также имеют некоторые другие довольно тонкие эффекты, которые часто нежелательны.

Andy 02.05.2018 09:38

@Andy Спасибо :) Я перешел на UserControls.

r_laezza 02.05.2018 10:10
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
11
943
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вы можете, например, обработать событие LoadedPage, чтобы либо вызвать метод, либо вызвать команду модели представления после первоначальной загрузки представления:

public partial class View2 : Page
{
    public View2()
    {
        InitializeComponent();
        Loaded += View2_Loaded;
    }

    private void View2_Loaded(object sender, RoutedEventArgs e)
    {
        var viewModel = DataContext as ViewModel2;
        if (viewModel != null)
            viewModel.YourCommand.Execute(null);
        Loaded -= View2_Loaded;
    }
}

Другой вариант - справиться с этим в MainViewModel. Вы привязываете свойство SelectedItemTabControl к свойству MainViewModel и устанавливаете это свойство для экземпляра ViewModel2 или ViewModel2, в зависимости от того, какой вид представления вы хотите отобразить.

Затем вы можете вызвать любой метод или вызвать любую команду по своему усмотрению. Но это другая история, и тогда вам не следует жестко кодировать TabItems в представлении и использовать элементы Frame для отображения Pages. Вот пример:

Выбор TabItem в TabControl из ViewModel

Привет, Магнус. Я не уверен, что страница загрузится снова, когда вы выберете вкладку. Хотя я редко использовал страницы.

Andy 30.04.2018 16:34

@Andy: Если вы обратите внимание на мой пример кода, вы увидите, что я фактически отключаю обработчик событий, когда происходит событие Loaded. Это означает, что команда будет вызвана только один раз, когда вкладка будет выбрана в первый раз. Если вы хотите вызывать команду каждый раз при выборе вкладки, вы можете просто удалить эту строку кода. И да, это должно работать, поскольку TabControl фактически выгружает вкладку, которая не выбрана, а затем перезагружает ее, когда она снова выбирается.

mm8 30.04.2018 16:37

Это кажется хорошим решением, поскольку оно довольно простое. Но поскольку это делается в коде, не нарушает ли это паттерн MVVM? Ваш комментарий о том, что «TabControl фактически выгружает вкладку, которая не выбрана, а затем перезагружает ее, когда она снова выбирается», очень полезен, спасибо :)

r_laezza 02.05.2018 10:37

@r_laezza: Это не нарушает шаблон MVVM. Код программной части представления принадлежит к тому же классу, что и XAML, поэтому не имеет значения, выполняете ли вы команду из кода программной части или XAML. Выполнение этого в коде Behing позволяет вам отказаться от подписки в обработчике событий после того, как команда была вызвана один раз.

mm8 04.05.2018 16:01

Хорошо, поэтому я создал настраиваемый элемент управления вкладкой. Я напишу для этого пошаговые инструкции, а затем вы сможете добавить в нее правку.

  1. Щелкните правой кнопкой мыши свое решение и выберите добавить новый проект.
  2. Поиск библиотеки настраиваемых элементов управления
  3. Выделите имя класса, который появляется, и щелкните правой кнопкой мыши, переименуйте его, как хотите, я назвал его MyTabControl.
  4. Добавьте Prism.Wpf в новый проект
  5. Добавьте ссылку на новый проект туда, где она вам понадобится. Мне нужно было добавить только основное приложение, но если у вас есть отдельный проект, в котором есть только представления, вам нужно будет добавить его и к нему.
  6. Наследуйте свой пользовательский элемент управления от TabControl, например:

    открытый класс MyTabControl: TabControl

  7. Вы заметите, что в проекте есть папка Themes, вам нужно будет открыть Generic.xaml и отредактировать ее. это должно выглядеть так:

    TargetType = "{x:Type local:MyTabControl}" BasedOn = "{StaticResource {x:Type TabControl}}" по какой-то причине это не позволяет мне показывать теги стиля, но они тоже должны быть там

  8. Пожалуйста, просмотрите этот код, я получил это от Добавить команду в настраиваемый элемент управления

    public class MyTabControl : TabControl
     {
         static MyTabControl()
         {
             DefaultStyleKeyProperty.OverrideMetadata(typeof(MyTabControl), new FrameworkPropertyMetadata(typeof(MyTabControl)));
         }
    
         public static readonly DependencyProperty TabChangedCommandProperty = DependencyProperty.Register(
             "TabChangedCommand", typeof(ICommand), typeof(MyTabControl), 
             new PropertyMetadata((ICommand)null,
             new PropertyChangedCallback(CommandCallBack)));
    
         private static void CommandCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
         {
             var myTabControl = (MyTabControl)d;
             myTabControl.HookupCommands((ICommand) e.OldValue, (ICommand) e.NewValue);
         }
    
         private void HookupCommands(ICommand oldValue, ICommand newValue)
         {
            if (oldValue != null)
             {
                 RemoveCommand(oldValue, oldValue);
             }
             AddCommand(oldValue, oldValue);
         }
    
         private void AddCommand(ICommand oldValue, ICommand newCommand)
         {
             EventHandler handler = new EventHandler(CanExecuteChanged);
             var canExecuteChangedHandler = handler;
             if (newCommand != null)
             {
                 newCommand.CanExecuteChanged += canExecuteChangedHandler;
             }
    
         }
    
         private void CanExecuteChanged(object sender, EventArgs e)
         {
             if (this.TabChangedCommand != null)
             {
                 if (TabChangedCommand.CanExecute(null))
                 {
                     this.IsEnabled = true;
                 }
                 else
                 {
                     this.IsEnabled = false;
                 }
             }
         }
    
         private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
         {
             EventHandler handler = CanExecuteChanged;
             oldCommand.CanExecuteChanged -= handler;
         }
    
         public ICommand TabChangedCommand
         {
             get { return (ICommand) GetValue(TabChangedCommandProperty); }
             set { SetValue(TabChangedCommandProperty, value); }
         }
    
    
         public override void OnApplyTemplate()
         {
             base.OnApplyTemplate();
             this.SelectionChanged += OnSelectionChanged;
         }
    
         private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
         {
             if (TabChangedCommand != null)
             {
                 TabChangedCommand.Execute(null);
             }
         }
         }
    

вам нужно будет добавить пространство имен в ваше окно или пользовательский элемент управления, например:

xmlns:wpfCustomControlLibrary1 = "clr-namespace:WpfCustomControlLibrary1;assembly=WpfCustomControlLibrary1"

и вот ваш контроль:

    <wpfCustomControlLibrary1:MyTabControl TabChangedCommand = "{Binding TabChangedCommand}">
        <TabItem Header = "View A"></TabItem>
        <TabItem Header = "View B"></TabItem>
    </wpfCustomControlLibrary1:MyTabControl>

Вот как я подхожу к такому требованию: Вид:

    <Window.DataContext>
        <local:MainWIndowViewModel/>
    </Window.DataContext>
    <Grid>
        <TabControl Name = "tc" ItemsSource = "{Binding vms}">
            <TabControl.Resources>
                <DataTemplate DataType = "{x:Type local:uc1vm}">
                    <local:UserControl1/>
                </DataTemplate>
                <DataTemplate DataType = "{x:Type local:uc2vm}">
                    <local:UserControl2/>
                </DataTemplate>
            </TabControl.Resources>
            <TabControl.ItemContainerStyle>
                <Style TargetType = "TabItem">
                    <Setter Property = "Header" Value = "{Binding TabHeading}"/>
                </Style>
            </TabControl.ItemContainerStyle>
        </TabControl>
    </Grid>
</Window>

Если у него есть uc1vm, он будет помещен в usercontrol1 в представлении. Я привязываюсь к набору моделей просмотра, которые реализуют интерфейс, поэтому я точно знаю, что могу применить к нему и вызвать метод.

Основная модель просмотра окна:

    private IDoSomething selectedVM;

    public IDoSomething SelectedVM
    {
        get { return selectedVM; }
        set
        {
            selectedVM = value;
            selectedVM.doit();
            RaisePropertyChanged();
        }
    }

    public ObservableCollection<IDoSomething> vms { get; set; } = new ObservableCollection<IDoSomething>
    {   new uc1vm(),
        new uc2vm()
    };

    public MainWIndowViewModel()
    {

    }

Когда вкладка выбрана, установщику для выбранного элемента будет передано новое значение. Отбросьте это и вызовите метод.

Мой интерфейс очень простой, так как это просто иллюстративно:

public interface IDoSomething
{
    void doit();
}

Пример модели представления, которая опять-таки просто иллюстративна и мало что делает:

public class uc1vm : IDoSomething
{
    public string TabHeading { get; set; } = "Uc1";
    public void doit()
    {
       // Your code goes here
    }
}
Ответ принят как подходящий

Я ценю ваш вклад, но я нашел альтернативное решение. Учитывая информацию, предоставленную @ mm8, я воспользовался событием Loaded, но таким способом, который не требует какого-либо кода в исходном коде.

Мое решение

В View, которому я хотел бы дать возможность выполнять метод каждый раз, когда пользователь выбирает содержащий его TabItem, я добавил следующий код:

<i:Interaction.Triggers>
    <i:EventTrigger EventName = "Loaded">
        <i:InvokeCommandAction Command = "{Binding OnLoadedCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

А затем просто внедрил DelegateCommand под названием OnLoadedCommand в соответствующий ViewViewModel. Внутри этой команды я вызываю желаемый метод.

Прокомментируйте, если вы заметили что-то не так в этом подходе! Я решил попробовать это, поскольку это потребовало наименьшего количества изменений в моем коде, но мне может не хватать важной информации о проблемах, которые может вызвать решение.

Если вам нужен / нужен элемент управления вкладкой (в отличие от региона, в котором вы перемещаетесь вручную), это определенно путь.

Haukinger 02.05.2018 14:35

Это будет вызывать команду каждый раз при выборе вкладки. Если это не проблема, вы можете использовать триггер взаимодействия в XAML. Однако программное выполнение команды из кода программной части представления не является нарушением шаблона MVVM. MVVM - это нет об исключении кода, связанного с Посмотреть, из представлений. Речь идет о разделении забот.

mm8 04.05.2018 16:03

Другие вопросы по теме