简体   繁体   English

我需要导航到另一个包含WPF与MVVM的父对象的子对象的视图

[英]I need to navigate to another view contains child objects of parent object in WPF with MVVM

I working on WPF project and trying to not break MVVM concepts with zero code in Views. 我正在研究WPF项目并试图在视图中使用零代码来破坏MVVM概念。

In conclusion i have a grid lists a list of job object properties and i want when i click on show logs button inside every grid row it shows to me another grid which contains logs for this job without breaking MVVM concept. 总而言之,我有一个网格列出了一个作业对象属性的列表,我希望当我点击每个网格行中的show logs按钮时,它向我显示另一个网格,其中包含此作业的日志而不破坏MVVM概念。

I only want to show another grid contains a child property which is a list of objects, it's straightforward easy thing in all other techniques MVC, MVP but here in MVVM it's some sort of strange, i searched for that for about 20 questions and no straightforward solution 我只想显示另一个网格包含一个子属性,这是一个对象列表,它在所有其他技术MVC,MVP中简单易行,但在MVVM中它有点奇怪,我搜索了大约20个问题,没有直截了当解

在此输入图像描述

Details: I have a MainView.xaml (Window), JobsView.xaml (UserControl), LogsView.xaml(UserControl) and i have corresponding ViewModel for each one. 细节:我有一个MainView.xaml(Window),JobsView.xaml(UserControl),LogsView.xaml(UserControl),每个都有相应的ViewModel。

Job class contains id, status, ... and a list of Log object: 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; }
}

I shown a JobsView.xaml (UserControl) in the MainView.xaml to list all job objects properties and i created a custom button for each job to shown logs. 我在MainView.xaml中显示了一个JobsView.xaml(UserControl)来列出所有作业对象属性,并为每个作业创建了一个自定义按钮来显示日志。

<Controls:MetroWindow ...>
<Grid>
    <DockPanel>
             <my:JobView />
    </DockPanel>
</Grid>

JobView.xaml markup: 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>

I want when any body click on Show Logs button it shown LogsView.xaml user control inside MainView.xaml instead of JobsView. 我希望当任何正文单击Show Logs按钮时,它会在MainView.xaml中显示LogsView.xaml用户控件而不是JobsView。

In LogViewModel i have a contructor to take jobId and return the logs: 在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;


}

But now i trying to make a navigation service and tries some techniques but it didn't works. 但现在我尝试制作导航服务并尝试一些技术,但它没有用。

Something like this might work: WPF MVVM navigate views 这样的东西可能会起作用: 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>

Then write the ShowLogsCommand so that it creates a new LogViewModel based on the currently selected job and then sets it to the ViewModel property (in MainViewModel ). 然后编写ShowLogsCommand以便根据当前选定的作业创建一个新的LogViewModel ,然后将其设置为ViewModel属性(在MainViewModel )。
Make sure to properly implement INotifyPropertyChanged. 确保正确实现INotifyPropertyChanged。

Example for ShowLogsCommand (I did not test this, use with care): ShowLogsCommand示例(我没有对此进行测试,请谨慎使用):

ICommand ShowLogsCommand => new RelayCommand(showLogsCommand);

private void showLogsCommand(Job job)
{
    ViewModel = new LogViewModel(job.JobId);
}

Change the xaml to: 将xaml更改为:

<Button Content="Show Logs"
        Command="{Binding ShowLogsCommand}"
        CommandParameter="{Binding}"
/>

please try the next solution: 请尝试下一个解决方案:

Xaml (is based on data template selector) 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 code 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 implementation and Relay command code 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;
}

Small explanation 小解释

  1. We have three DataTemplate Jobs, Logs, Default. 我们有三个DataTemplate作业,Logs,Default。
  2. The DataTemplateSelector will manage the data template selection based on the Content property of the ContentControl which the DataTemplateSelector is attached to it. DataTemplateSelector将根据DataTemplateSelector附加到其上的ContentControl的Content属性来管理数据模板选择。
  3. The button is inside the grid and it is bound to the command from the parent's VM. 按钮位于网格内,并且绑定到父虚拟机的命令。 The parent VM is provided to the DataTemplate via Freezable proxie object. 父VM通过Freezable proxie对象提供给DataTemplate。

Let me know if you have problems with the code. 如果您的代码有问题,请告诉我。

Regards. 问候。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM