[英]I need to navigate to another view contains child objects of parent object in WPF with MVVM
我正在研究WPF项目并试图在视图中使用零代码来破坏MVVM概念。
总而言之,我有一个网格列出了一个作业对象属性的列表,我希望当我点击每个网格行中的show logs按钮时,它向我显示另一个网格,其中包含此作业的日志而不破坏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中显示LogsView.xaml用户控件而不是JobsView。
在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;
}
但现在我尝试制作导航服务并尝试一些技术,但它没有用。
这样的东西可能会起作用: WPF MVVM导航视图
<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}"
/>
请尝试下一个解决方案:
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 JobModel{Id = 1, Salary = "12k", Title = "Hw Engineer"},
new JobModel{Id=2, Salary = "18k", Title = "Sw Engineer"},
new JobModel{Id = 3, Salary = "12k", Title = "IT Engineer"},
new JobModel{Id=4, Salary = "18k", Title = "QA Engineer"},
};
_logModels = new List<LogModel>
{
new LogModel{Id = 1, Salary = "12k", Title = "Hw Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Pending"},
new LogModel{Id = 1, Salary = "12k", Title = "Hw Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Active"},
new LogModel{Id = 1, Salary = "12k", Title = "Hw Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Closed"},
new LogModel{Id=2, Salary = "12k", Title = "Sw Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Pending"},
new LogModel{Id=2, Salary = "12k", Title = "Sw Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Active"},
new LogModel{Id=2, Salary = "12k", Title = "Sw Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Closed"},
new LogModel{Id = 3, Salary = "12k", Title = "IT Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Pending"},
new LogModel{Id = 3, Salary = "12k", Title = "IT Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Active"},
new LogModel{Id = 3, Salary = "12k", Title = "IT Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Closed"},
new LogModel{Id=4, Salary = "12k", Title = "QA Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Pending"},
new LogModel{Id=4, Salary = "12k", Title = "QA Engineer", LogTime = DateTime.Now.ToLocalTime(), LogEvent = "Active"},
new LogModel{Id=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实现和中继命令代码
/// <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;
}
小解释
如果您的代码有问题,请告诉我。
问候。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.