繁体   English   中英

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

[英]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;
}

小解释

  1. 我们有三个DataTemplate作业,Logs,Default。
  2. DataTemplateSelector将根据DataTemplateSelector附加到其上的ContentControl的Content属性来管理数据模板选择。
  3. 按钮位于网格内,并且绑定到父虚拟机的命令。 父VM通过Freezable proxie对象提供给DataTemplate。

如果您的代码有问题,请告诉我。

问候。

暂无
暂无

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

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