【问题标题】:wpf - One ViewModel for dynamic Tabswpf - 一个用于动态选项卡的 ViewModel
【发布时间】:2017-02-12 18:39:27
【问题描述】:

是否可以有一个ViewModel 用于多个动态Tabs?这意味着,每当我创建一个新选项卡时,它都应该使用相同的 ViewModel 实例,这样我就可以检索信息并防止每个选项卡共享数据/显示相同的数据。

我正在考虑使用它的设置是用于工资单应用程序,其中可以从每个选项卡更新每个员工的工资单。所以每个Tab的信息应该是不同的。

这可能吗?


更新:添加代码

处理 Tabs Collection 的 MainViewModel:

public ObservableCollection<WorkspaceViewModel> Workspaces { get; set; }
public MainViewModel()
{
    Workspaces = new ObservableCollection<WorkspaceViewModel>();
    Workspaces.CollectionChanged += Workspaces_CollectionChanged;
}

void Workspaces_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null && e.NewItems.Count != 0)
            foreach (WorkspaceViewModel workspace in e.NewItems)
                workspace.RequestClose += this.OnWorkspaceRequestClose;

        if (e.OldItems != null && e.OldItems.Count != 0)
            foreach (WorkspaceViewModel workspace in e.OldItems)
                workspace.RequestClose -= this.OnWorkspaceRequestClose;
    }

    private void OnWorkspaceRequestClose(object sender, EventArgs e)
    {
        CloseWorkspace();
    }

    private DelegateCommand _exitCommand;
    public ICommand ExitCommand
    {
        get { return _exitCommand ?? (_exitCommand = new DelegateCommand(() => Application.Current.Shutdown())); }
    }

    private DelegateCommand _newWorkspaceCommand;
    public ICommand NewWorkspaceCommand
    {
        get { return _newWorkspaceCommand ?? (_newWorkspaceCommand = new DelegateCommand(NewWorkspace)); }
    }

    private void NewWorkspace()
    {
        var workspace = new WorkspaceViewModel();

        Workspaces.Add(workspace);

        SelectedIndex = Workspaces.IndexOf(workspace);

    }

    private DelegateCommand _closeWorkspaceCommand;
    public ICommand CloseWorkspaceCommand
    {
        get { return _closeWorkspaceCommand ?? (_closeWorkspaceCommand = new DelegateCommand(CloseWorkspace, () => Workspaces.Count > 0)); }
    }

    private void CloseWorkspace()
    {
        Workspaces.RemoveAt(SelectedIndex);
        SelectedIndex = 0;
    }

    private int _selectedIndex = 0;
    public int SelectedIndex
    {
        get { return _selectedIndex; }
        set
        {
            _selectedIndex = value;
            OnPropertyChanged("SelectedIndex");
        }
    }

WorkspaceViewModel:

public PayslipModel Payslip { get; set; }

    public WorkspaceViewModel()
    {
        Payslip = new PayslipModel();
        SaveToDatabase = new DelegateCommand(Save, () => CanSave);

        SelectAll = new DelegateCommand(Select, () => CanSelect);

        UnSelectAll = new DelegateCommand(UnSelect, () => CanUnSelect);
    }

    public ICommand SaveToDatabase
    {
        get; set;
    }

    private bool CanSave
    {
        get { return true; }
    }

    private async void Save()
    {
        try
        {
            MessageBox.Show(Payslip.Amount.ToString());
        }
        catch (DbEntityValidationException ex)
        {
            foreach (var en in ex.EntityValidationErrors)
            {
                var exceptionDialog = new MessageDialog
                {
                    Message = { Text = string.Format("{0}, {1}", en.Entry.Entity.GetType().Name, en.Entry.State) }
                };

                await DialogHost.Show(exceptionDialog, "RootDialog");

                foreach (var ve in en.ValidationErrors)
                {
                    exceptionDialog = new MessageDialog
                    {
                        Message = { Text = string.Format("{0}, {1}", ve.PropertyName, ve.ErrorMessage) }
                    };

                    await DialogHost.Show(exceptionDialog, "RootDialog");
                }
            }
        }
        catch (Exception ex)
        {
            var exceptionDialog = new MessageDialog
            {
                Message = { Text = string.Format("{0}", ex) }
            };

            await DialogHost.Show(exceptionDialog, "RootDialog");
        }

    }


    public event EventHandler RequestClose;
    private void OnRequestClose()
    {
        if (RequestClose != null)
            RequestClose(this, EventArgs.Empty);
    }

    private string _header;
    public string Header
    {
        get { return _header; }
        set
        {
            _header = value;
            OnPropertyChanged("Header");
        }
    }

Payroll UserControl,其中 WorkspaceViewModel 为 DataContext:

public Payroll()
{
    InitializeComponent();
    DataContext = new WorkspaceViewModel();
}

Payroll.xaml 选项卡控件:

<dragablz:TabablzControl ItemsSource="{Binding Workspaces}" SelectedIndex="{Binding SelectedIndex}" BorderBrush="{x:Null}">
            <dragablz:TabablzControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Header}"/>
                </DataTemplate>
            </dragablz:TabablzControl.ItemTemplate>
            <dragablz:TabablzControl.ContentTemplate>
                <DataTemplate>
                    <ContentControl Margin="16">
                        <local:TabLayout DataContext="{Binding Path=Payslip, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" x:Name="tabLayout"/>
                    </ContentControl>
                </DataTemplate>
            </dragablz:TabablzControl.ContentTemplate>
        </dragablz:TabablzControl>

这按预期工作,每个选项卡显示不同的信息并且绑定工作正常。但是,我无法检索 MessageBox 中的信息。

【问题讨论】:

  • 您不能将同一个实例多次添加到同一个源集合中吗?或者,如果您不希望它们共享数据,为什么要对多个选项卡使用相同的模型...?

标签: c# wpf mvvm


【解决方案1】:

我不确定我是否完全理解您的问题,但如果您需要一个带有 tabcontrol 的 Window,其中每个 tab 都指一个员工,那么您必须将 tabcontrol 的 ItemsSource 绑定到 ViewModel 的列表.

不可能将所有标签页绑定到同一个实例,因为这样所有标签页都会做同样的事情,并显示相同的信息。

【讨论】:

  • 这听起来像是我想要实现的目标。如何将 ItemsSource 绑定到 ViewModel 列表?还是我需要就此提出一个新问题?
  • 其实我只是做了一些检查,这就是我目前所拥有的,但有一个问题。每次添加选项卡时,ViewModel 都会再次实例化,因此当我将正在显示的信息保存到数据库时,我会收到 NullPointerException。保存按钮在另一个视图中,该视图的 DataContext 处理 TabControl 项(WorkspaceViewModel)
【解决方案2】:

我无法让它按我的方式工作,所以我将保存按钮放在视图中,该视图将 DataContext 设置为加载员工信息的位置并让它从那里工作,因为它直接访问属性.

【讨论】:

    【解决方案3】:

    ViewModel 应该与模型具有 1:1 的关系。在 TabControl 的 DataContext 中,假设您有如下属性:

    public ObservableCollection<EmployeeViewModel> Employees {get;set;}
    public EmployeeViewModel CurrentEmployee
    { 
        get { return _currentEmployee;}
        set
        { 
            _currentEmployee = value;
            OnPropertyChanged("CurrentEmployee");
        }
    }
    

    Employees 绑定到 TabControl 的 ItemsSource,CurrentEmployee 绑定到 CurrentItem。创建新标签页:

    var employee = new Employee();
    var vm = new EmployeeViewModel(employee);
    Employees.Add(vm);
    CurrentEmployee = vm;
    

    如果您想要 TabControl 之外的保存按钮,只需将其 DataContext 设置为 CurrentEmployee。

    我希望这会有所帮助!

    编辑:

    我认为有两件事会导致问题:

    1. Payroll.xaml 应该绑定到 MainViewModel,因为这是 Workspaces 集合所在的位置。

    2. 不要在视图代码中实例化 ViewModel。请改用 DataTemplate(请参阅 this question)。

    看看Josh Smith's MVVM demo app(source code)

    【讨论】:

    • 谢谢!如果可行,我会尝试并接受作为答案:)
    • 我添加了一些代码来展示我目前所拥有的。我尝试了您的解决方案,但绑定似乎消失了。
    猜你喜欢
    • 1970-01-01
    • 2019-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-30
    • 1970-01-01
    • 2018-10-24
    相关资源
    最近更新 更多