Я пытался реализовать это какое-то время и до сих пор не мог этого сделать, несмотря на то, что мне казалось, что это должно быть что-то легкое.
Сложность связана с тем, что я реализовал приложение 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, чтобы это произошло.
Я заранее ценю всю помощь.
Где определяется метод? В представлении или в модели представления?
@ 3xGuy Я добавил код xaml, но код ViewModel довольно длинный. Что именно вы хотите?
спасибо, я разберусь, дайте мне несколько.
@ mm8 Метод определен во ViewModel
@ mm8 Да, я привязал к свойству ButtonCommand объект DelegateCommand (Prism), определенный в моей ViewModel.
Tabcontrol - это тип селектора. msdn.microsoft.com/en-us/library/… Когда вы выбираете элемент табуляции, вы делаете элемент выбранным. Вы можете привязать selecteditem к свойству в вашей модели просмотра и действовать для этого в сеттере. Вы также можете выполнить привязку и шаблон для создания элементов табуляции.
Видя, что у вас есть фреймы, я думаю, это потому, что вы используете страницы. Редко хорошая идея, обычно лучше использовать элементы управления пользователем. Тогда вы можете просто поместить пользовательский элемент управления прямо в tabitem.
@ Энди, я могу легко изменить свои просмотры на UserControl. Не могли бы вы пояснить, почему использовать Page - плохая идея?
Contentcontrol и usercontrols - это обычный способ работы, потому что они легче и гибче. Страницы были изобретены на заре WPF, когда они предназначались для использования в браузере, а также на рабочем столе или в браузере. Редко хороший план. Они ограничивают в том, что вы можете поместить их только в рамки. Рамы представляют собой относительно высокий контейнер с накладными расходами из-за своего журнала. Если вы не собираетесь использовать это средство ведения журнала, тогда они просто накладные расходы по сравнению с контролем содержимого. Рамки также имеют некоторые другие довольно тонкие эффекты, которые часто нежелательны.
@Andy Спасибо :) Я перешел на UserControls.





Вы можете, например, обработать событие 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: Если вы обратите внимание на мой пример кода, вы увидите, что я фактически отключаю обработчик событий, когда происходит событие Loaded. Это означает, что команда будет вызвана только один раз, когда вкладка будет выбрана в первый раз. Если вы хотите вызывать команду каждый раз при выборе вкладки, вы можете просто удалить эту строку кода. И да, это должно работать, поскольку TabControl фактически выгружает вкладку, которая не выбрана, а затем перезагружает ее, когда она снова выбирается.
Это кажется хорошим решением, поскольку оно довольно простое. Но поскольку это делается в коде, не нарушает ли это паттерн MVVM? Ваш комментарий о том, что «TabControl фактически выгружает вкладку, которая не выбрана, а затем перезагружает ее, когда она снова выбирается», очень полезен, спасибо :)
@r_laezza: Это не нарушает шаблон MVVM. Код программной части представления принадлежит к тому же классу, что и XAML, поэтому не имеет значения, выполняете ли вы команду из кода программной части или XAML. Выполнение этого в коде Behing позволяет вам отказаться от подписки в обработчике событий после того, как команда была вызвана один раз.
Хорошо, поэтому я создал настраиваемый элемент управления вкладкой. Я напишу для этого пошаговые инструкции, а затем вы сможете добавить в нее правку.
MyTabControl.Наследуйте свой пользовательский элемент управления от TabControl, например:
открытый класс MyTabControl: TabControl
Вы заметите, что в проекте есть папка Themes, вам нужно будет открыть Generic.xaml и отредактировать ее. это должно выглядеть так:
TargetType = "{x:Type local:MyTabControl}" BasedOn = "{StaticResource {x:Type TabControl}}" по какой-то причине это не позволяет мне показывать теги стиля, но они тоже должны быть там
Пожалуйста, просмотрите этот код, я получил это от Добавить команду в настраиваемый элемент управления
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. Внутри этой команды я вызываю желаемый метод.
Прокомментируйте, если вы заметили что-то не так в этом подходе! Я решил попробовать это, поскольку это потребовало наименьшего количества изменений в моем коде, но мне может не хватать важной информации о проблемах, которые может вызвать решение.
Если вам нужен / нужен элемент управления вкладкой (в отличие от региона, в котором вы перемещаетесь вручную), это определенно путь.
Это будет вызывать команду каждый раз при выборе вкладки. Если это не проблема, вы можете использовать триггер взаимодействия в XAML. Однако программное выполнение команды из кода программной части представления не является нарушением шаблона MVVM. MVVM - это нет об исключении кода, связанного с Посмотреть, из представлений. Речь идет о разделении забот.
Не могли бы вы выложить
xaml? а один изViewModels?