Как запускать метод каждый раз при выборе TabItem в приложении MVVM с использованием Prism - PullRequest
0 голосов
/ 30 апреля 2018

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

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

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

У меня есть три представления с соответствующими им моделями представления (проводными с использованием метода AutoWireViewModel Prism). 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, чтобы это произошло.

Я заранее благодарен за всю помощь.

Ответы [ 4 ]

0 голосов
/ 02 мая 2018

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

Мое решение

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

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

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

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

0 голосов
/ 30 апреля 2018

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

  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>
0 голосов
/ 02 мая 2018

Вот как я бы подошел к такому требованию: Вид:

    <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
    }
}
0 голосов
/ 30 апреля 2018

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

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. Вы связываете свойство SelectedItem TabControl со свойством MainViewModel и задаете для этого свойства экземпляр ViewModel2 или ViewModel2, в зависимости от того, какой вид вы хотите отобразить.

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

Выбор TabItem в TabControl из ViewModel

...