我需要导航到另一个视图,其中包含带有 MVVM 的 WPF 中父对象的子对象
Posted
技术标签:
【中文标题】我需要导航到另一个视图,其中包含带有 MVVM 的 WPF 中父对象的子对象【英文标题】:I need to navigate to another view contains child objects of parent object in WPF with MVVM 【发布时间】:2016-05-30 16:20:50 【问题描述】:我从事 WPF 项目,并试图在 Views 中使用零代码不破坏 MVVM 概念。
总之,我有一个网格列出了作业对象属性的列表,当我单击每个网格行内的显示日志按钮时,它会向我显示另一个网格,其中包含该作业的日志,而不会破坏 MVVM 概念。
我只想显示另一个网格包含一个子属性,它是一个对象列表,这在所有其他技术 MVC、MVP 中是很简单的事情,但在 MVVM 中它有点奇怪,我搜索了大约 20 个问题并且没有直接的解决方案
详情: 我有一个 MainView.xaml (Window)、JobsView.xaml (UserControl)、LogsView.xaml(UserControl),每个都有对应的 ViewModel。
Job 类包含 id、status、...和一个 Log 对象列表:
public class Job
public Job()
Logs = new List<Log>();
[Key]
public Guid JobID get; set;
public JobStatus Status get; set;
public virtual ICollection<Log> Logs get; set;
我在 MainView.xaml 中显示了 JobsView.xaml (UserControl) 以列出所有作业对象属性,并为每个作业创建了一个自定义按钮以显示日志。
<Controls:MetroWindow ...>
<Grid>
<DockPanel>
<my:JobView />
</DockPanel>
</Grid>
JobView.xaml 标记:
<UserControl x:Class=...>
<Grid>
<DataGrid x:Name="jobsDataGrid"
ItemsSource="Binding Jobs"
SelectedItem="Binding selectedJob"
AutoGenerateColumns="False"
EnableRowVirtualization="True"
RowDetailsVisibilityMode="VisibleWhenSelected"
IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn x:Name="jobIdColumn"
Binding="Binding JobID"
Header="Job Id"
Width="SizeToHeader"
/>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Show Logs"
Command="Binding ShowLogsCommand"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
我希望当任何主体单击 Show Logs 按钮时,它会在 MainView.xaml 而不是 JobsView 中显示 LogsView.xaml 用户控件。
在 LogViewModel 中,我有一个构造函数来获取 jobId 并返回日志:
public class LogViewModel : BindableBase // INotifyPropertyChanged
private Log log = new Log();
private UnitOfWork unitOfWork = new UnitOfWork();
public LogViewModel()
if (DesignerProperties.GetIsInDesignMode(new System.Windows.DependencyObject())) return;
Logs = new ObservableCollection<Log>(unitOfWork.Logs.Get(null, ls => ls.OrderBy(l => l.LogID)).ToList());
public LogViewModel(Guid jobId)
if (DesignerProperties.GetIsInDesignMode(new System.Windows.DependencyObject())) return;
Logs = new ObservableCollection<Log>(unitOfWork.Logs.Get(l => l.JobID == jobId, ls => ls.OrderBy(l => l.LogID)).ToList());
public ObservableCollection<Log> Logs get; set;
// public event PropertyChangedEventHandler PropertyChanged;
但现在我尝试制作导航服务并尝试了一些技术但没有成功。
【问题讨论】:
有答案吗:( 尝试在这里问social.msdn.microsoft.com/Forums/vstudio/en-US/home?forum=wpf 您在寻找RowDetailTemplate
吗? wpf-tutorial.com/datagrid-control/details-row
【参考方案1】:
这样的事情可能会起作用:WPF MVVM navigate views
<Controls:MetroWindow ...>
<Controls:MetroWindow.Resources>
<DataTemplate DataType="x:Type my:LogViewModel">
<my:LogView/>
</DataTemplate>
<DataTemplate DataType="x:Type my:JobViewModel">
<my:JobView/>
</DataTemplate>
</Controls:MetroWindow.Resources>
<Grid>
<DockPanel>
<ContentControl Content="Binding ViewModel" />
</DockPanel>
</Grid>
然后编写ShowLogsCommand
,以便它根据当前选定的作业创建一个新的LogViewModel
,然后将其设置为ViewModel
属性(在MainViewModel
中)。
确保正确实现 INotifyPropertyChanged。
ShowLogsCommand
的示例(我没有对此进行测试,请谨慎使用):
ICommand ShowLogsCommand => new RelayCommand(showLogsCommand);
private void showLogsCommand(Job job)
ViewModel = new LogViewModel(job.JobId);
将 xaml 更改为:
<Button Content="Show Logs"
Command="Binding ShowLogsCommand"
CommandParameter="Binding"
/>
【讨论】:
我试过了,但失败了,你能举一个 ShowLogsCommand () 实现的示例 @Marzouk 更新了我的答案。 我想声明 JobsUserControl (JobView) 中的“显示日志”按钮,所以 Command="Binding ShowLogsCommand" 将在 JobViewModel 中定义,这是我的主要问题 您可以在 MainViewModel 中定义命令并相应地设置绑定,或者在 JobViewModel 中使 MainViewModel 为已知。 我认为这是我从一开始就尝试做的,但不幸的是它没有奏效。我只想显示另一个网格包含所选对象的子对象,这在所有其他技术中都很简单,但在 MVVM 中它有点奇怪,我搜索了大约 20 个问题并且没有直接的解决方案【参考方案2】:请尝试下一个解决方案:
Xaml(基于数据模板选择器)
<Window x:Class="MvvmNavigationIssue.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mvvmNavigationIssue="clr-namespace:MvvmNavigationIssue"
Title="MainWindow" Height="350" Width="525" x:Name="This">
<Window.DataContext>
<mvvmNavigationIssue:MainNavigationViewModel/>
</Window.DataContext>
<Window.Resources>
<mvvmNavigationIssue:FreezableProxyClass x:Key="ProxyElement"
ProxiedDataContext="Binding Source=x:Reference This, Path=DataContext"/>
<DataTemplate x:Key="DefaultDataTemplate">
<Grid>
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="Tomato" />
<TextBlock Text="Default Template" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
</Grid>
</DataTemplate>
<DataTemplate x:Key="JobsDataTemplate">
<ListView ItemsSource="Binding JobModels, UpdateSourceTrigger=PropertyChanged">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" DisplayMemberBinding="Binding Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="mvvmNavigationIssue:JobModel">
<TextBlock Text="Binding Id"></TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Title" DisplayMemberBinding="Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged" Width="100"/>
<GridViewColumn Header="Salary" DisplayMemberBinding="Binding Salary, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged" Width="100"/>
<GridViewColumn Header="" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="mvvmNavigationIssue:JobModel">
<Button Command="Binding Source=StaticResource ProxyElement,
Path=ProxiedDataContext.ShowLogsCommand, Mode=OneWay,
UpdateSourceTrigger=PropertyChanged" CommandParameter="Binding ">Logs</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
<DataTemplate x:Key="LogsDataTemplate">
<ListView ItemsSource="Binding LogModels, UpdateSourceTrigger=PropertyChanged">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" DisplayMemberBinding="Binding Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="mvvmNavigationIssue:JobModel">
<TextBlock Text="Binding Id"></TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Title" DisplayMemberBinding="Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged" Width="100"/>
<GridViewColumn Header="Time" DisplayMemberBinding="Binding LogTime, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged" Width="100"/>
<GridViewColumn Header="Event" DisplayMemberBinding="Binding LogEvent, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged" Width="100"/>
<GridViewColumn Header="" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="mvvmNavigationIssue:JobModel">
<Button Command="Binding Source=StaticResource ProxyElement,
Path=ProxiedDataContext.ShowAllJobsCommand, Mode=OneWay,
UpdateSourceTrigger=PropertyChanged">All Jobs</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
<mvvmNavigationIssue:MainContentTemplateSelector x:Key="MainContentTemplateSelectorKey"
DefaultDataTemplate="StaticResource DefaultDataTemplate"
JobsViewDataTemplate="StaticResource JobsDataTemplate"
LogsViewDataTemplate="StaticResource LogsDataTemplate"/>
</Window.Resources>
<Grid>
<ContentControl Content="Binding CurrentViewModel, UpdateSourceTrigger=PropertyChanged"
ContentTemplateSelector="StaticResource MainContentTemplateSelectorKey"></ContentControl>
</Grid>
MVVM 代码
public class FreezableProxyClass : Freezable
protected override Freezable CreateInstanceCore()
return new FreezableProxyClass();
public static readonly DependencyProperty ProxiedDataContextProperty = DependencyProperty.Register(
"ProxiedDataContext", typeof(object), typeof(FreezableProxyClass), new PropertyMetadata(default(object)));
public object ProxiedDataContext
get return (object)GetValue(ProxiedDataContextProperty);
set SetValue(ProxiedDataContextProperty, value);
public class MainNavigationViewModel : BaseObservableObject
private object _currentViewModel;
private JobsViewModel _jobsViewModel;
private List<LogModel> _logModels;
private ICommand _showLogs;
private ICommand _showJobs;
public MainNavigationViewModel()
_jobsViewModel = new JobsViewModel();
Init();
private void Init()
_jobsViewModel.JobModels = new ObservableCollection<JobModel>
new JobModelId = 1, Salary = "12k", Title = "Hw Engineer",
new JobModelId=2, Salary = "18k", Title = "Sw Engineer",
new JobModelId = 3, Salary = "12k", Title = "IT Engineer",
new JobModelId=4, Salary = "18k", Title = "QA Engineer",
;
_logModels = new List<LogModel>
new LogModelId = 1, Salary = "12k", Title = "Hw Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Pending",
new LogModelId = 1, Salary = "12k", Title = "Hw Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Active",
new LogModelId = 1, Salary = "12k", Title = "Hw Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Closed",
new LogModelId=2, Salary = "12k", Title = "Sw Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Pending",
new LogModelId=2, Salary = "12k", Title = "Sw Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Active",
new LogModelId=2, Salary = "12k", Title = "Sw Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Closed",
new LogModelId = 3, Salary = "12k", Title = "IT Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Pending",
new LogModelId = 3, Salary = "12k", Title = "IT Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Active",
new LogModelId = 3, Salary = "12k", Title = "IT Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Closed",
new LogModelId=4, Salary = "12k", Title = "QA Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Pending",
new LogModelId=4, Salary = "12k", Title = "QA Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Active",
new LogModelId=4, Salary = "12k", Title = "QA Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Closed",
;
CurrentViewModel = _jobsViewModel;
public object CurrentViewModel
get return _currentViewModel;
set
_currentViewModel = value;
OnPropertyChanged(()=>CurrentViewModel);
public ICommand ShowLogsCommand
get return _showLogs ?? (_showLogs = new RelayCommand<JobModel>(ShowLogs));
private void ShowLogs(JobModel obj)
CurrentViewModel = new LogsViewModel
LogModels = new ObservableCollection<LogModel>(_logModels.Where(model => model.Id == obj.Id)),
;
public ICommand ShowAllJobsCommand
get return _showJobs ?? (_showJobs = new RelayCommand(ShowAllJobs));
private void ShowAllJobs()
CurrentViewModel = _jobsViewModel;
public class LogsViewModel:BaseObservableObject
private ObservableCollection<LogModel> _logModels;
public ObservableCollection<LogModel> LogModels
get return _logModels;
set
_logModels = value;
OnPropertyChanged();
public class LogModel : JobModel
private DateTime _logTime;
private string _logEvent;
public DateTime LogTime
get return _logTime;
set
_logTime = value;
OnPropertyChanged();
public string LogEvent
get return _logEvent;
set
_logEvent = value;
OnPropertyChanged();
public class JobsViewModel:BaseObservableObject
private ObservableCollection<JobModel> _jobModels;
public ObservableCollection<JobModel> JobModels
get return _jobModels;
set
_jobModels = value;
OnPropertyChanged();
public class JobModel:BaseObservableObject
private int _id;
private string _title;
private string _salary;
public int Id
get return _id;
set
_id = value;
OnPropertyChanged();
public string Title
get return _title;
set
_title = value;
OnPropertyChanged();
public string Salary
get return _salary;
set
_salary = value;
OnPropertyChanged();
INPC实现和Relay命令代码
/// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
if (!EqualityComparer<T>.Default.Equals(field, value))
field = value;
OnPropertyChanged(name);
return true;
return false;
public class RelayCommand : ICommand
private readonly Func<bool> _canExecute;
private readonly Action _execute;
public RelayCommand(Action execute)
: this(() => true, execute)
public RelayCommand(Func<bool> canExecute, Action execute)
_canExecute = canExecute;
_execute = execute;
public bool CanExecute(object parameter = null)
return _canExecute();
public void Execute(object parameter = null)
_execute();
public event EventHandler CanExecuteChanged;
public class RelayCommand<T> : ICommand
where T:class
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public RelayCommand(Action<T> execute):this(obj => true, execute)
public RelayCommand(Predicate<T> canExecute, Action<T> execute)
_canExecute = canExecute;
_execute = execute;
public bool CanExecute(object parameter)
return _canExecute(parameter as T);
public void Execute(object parameter)
_execute(parameter as T);
public event EventHandler CanExecuteChanged;
小解释
-
我们有三个 DataTemplate 作业、日志、默认值。
DataTemplateSelector 将根据 DataTemplateSelector 附加到它的 ContentControl 的 Content 属性来管理数据模板选择。
该按钮位于网格内,并绑定到来自父虚拟机的命令。父 VM 通过 Freezable 代理对象提供给 DataTemplate。
如果您对代码有任何问题,请告诉我。
问候。
【讨论】:
以上是关于我需要导航到另一个视图,其中包含带有 MVVM 的 WPF 中父对象的子对象的主要内容,如果未能解决你的问题,请参考以下文章
带有 MVVM Light 的标签栏控制器导航 Xamarin
在 UserControl WPF MVVM caliburn 内的 UserControl 之间切换