Tab

Tab是系统中非常重要的容器控件,两种模式最终以Tab为最小容器进行管理。

  • Phone模式时负责填充整个页面内容,并且窗口内的所有Tab可互相导航;
  • Win模式时是浮动、停靠、拖拽组合时的最小单位;
  • 同一Tab区域可通过切换Tab实现区域内导航效果,并且支持带遮罩的模式视图、导航参数、导航结果;
  • 它更是业务开发时功能模块的容器,是模块视图的基类,相比于Fv LvTab属于容器型控件,支持各种导航,和Fv Lv一起是系统最核心最常用的控件。

前面在介绍界面框架时提到窗口内部导航Tab区域内导航,这两种导航使Windows模式和Phone模式衔接在一起,是搬运工平台UI的灵魂和重要基础,它使得我们既能保证传统的快速开发,又能适配不同的界面模式。

在实际业务开发过程中,我们通常将一个独立功能放在Win中实现,Win中有多个区域,包括列表、表单、搜索、关联数据等区域,每个区域是一个Tab,如果将所有Tab的内容都放在Win.xaml中,Win.xaml.cs会包含所有列表数据的加载、关联互动、搜索等功能代码,这样代码冗长非常混乱,也不利于后期的维护工作。如果将每个Tab的内容放在独立的xaml中实现,既模块清晰,也便于复用和维护。

1
2
3
4
5
6
7
8
9
/// <summary>
/// 模块视图的基类,功能模块的容器
/// <para>1. Phone模式时是页面内容Page.Content</para>
/// <para>2. Win模式时是浮动、停靠、拖拽组合时的最小单位</para>
/// <para>3. Phone模式时窗口内的所有Tab可互相导航</para>
/// <para>4. 同一Tab区域可通过切换Tab实现区域内导航效果</para>
/// <para>5. 区域内导航支持带遮罩的模式视图、导航参数、导航结果</para>
/// </summary>
public partial class Tab : TabItem, IPhonePage

注意
系统中把继承Tab实现的独立功能模块称为模块视图

导航

同一Win中所有Tab是紧耦合的,互相之间可直接调用,每个模块视图通过OwnWin获取所属的WinWin中以属性的方式公开所有模块视图,这样每个模块视图都可访问Win中的其他模块视图。

导航同样包含两类:Win内导航、Tab区域内导航。

Win内导航

Win内导航只在Phone模式有效,包括三个方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/// <summary>
/// 导航到指定Tab
/// </summary>
/// <param name="p_tab"></param>
public void NaviTo(Tab p_tab)

/// <summary>
/// 导航到多页Tab
/// </summary>
/// <param name="p_ls"></param>
public void NaviTo(List<Tab> p_ls)

/// <summary>
/// 导航到指定页,支持多页Tab形式
/// </summary>
/// <param name="p_tabTitle">多个页面时用逗号隔开(自动以Tab形式显示),null时自动导航到第一个Tab</param>
public void NaviTo(string p_tabTitle)

Tab区域内导航

Tab区域内导航对于Win模式和Phone模式都有效,Phone模式下也是独立页面,和Win内导航无区别,Win模式下切换整个模块视图,向前向后的导航方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/// <summary>
/// 向前导航到新Tab,可异步等待返回值
/// </summary>
/// <typeparam name="T">返回值的类型</typeparam>
/// <param name="p_tab">导航到新Tab</param>
/// <param name="p_params">输入参数,值为新Tab的NaviParams属性</param>
/// <param name="p_isModal">WinUI模式是否带遮罩,遮罩为了禁止对其他位置编辑(用Dlg实现)</param>
/// <returns>返回时的输出参数</returns>
public Task<T> Forward<T>(Tab p_tab, object p_params = null, bool p_isModal = false)

/// <summary>
/// 向前导航到新Tab
/// </summary>
/// <param name="p_tab">导航到新Tab</param>
/// <param name="p_params">输入参数,值为新Tab的NaviParams属性</param>
/// <param name="p_isModal">WinUI模式是否带遮罩,遮罩为了禁止对其他位置编辑(用Dlg实现)</param>
public void Forward(Tab p_tab, object p_params = null, bool p_isModal = false)

/// <summary>
/// 向后导航到上一Tab
/// </summary>
public async void Backward()

/// <summary>
/// 切换到指定Tab,切换后新Tab的上一Tab为首页
/// </summary>
/// <param name="p_tab"></param>
/// <param name="p_showBackBtn">是否显示返回按钮</param>
public void Toggle(Tab p_tab, bool p_showBackBtn = false)

/// <summary>
/// Tab区域内导航时返回到首页
/// </summary>
public void BackToHome()

Forward<T>方法对于需要输入输出参数的情况非常方便,输入参数提供给新TabNaviParams属性,一般在OnFirstLoaded时使用,输出参数为等待后的返回值,是新TabResult,参数定义:

1
2
3
4
5
6
7
8
9
/// <summary>
/// 导航时的入参,一般在 OnFirstLoaded 时使用
/// </summary>
public object NaviParams

/// <summary>
/// 导航返回值
/// </summary>
public object Result

含输入输出参数的导航如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
async void OnEditFolder(object sender, Mi e)
{
    if (await Forward<bool>(new EditFolder(_fileMgr), e.Row))
        LoadData();
}

public sealed partial class EditFolder : Tab
{
    protected override void OnFirstLoaded()
    {
        Row row = CreateData();
        if (NaviParams is Row r)
        {
            row.InitVal(0, r.ID);
            row.InitVal(1, r["name"]);
        }
        _fv.Data = row;
    }
}

Tab还可以通过以下方法放在Dlg中显示,同样支持Tab区域内导航:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public partial class Dlg : Control, IDlgPressed
{
    /// <summary>
    /// 加载Tab,PhoneUI直接显示,WinUI外套Tabs
    /// </summary>
    /// <param name="p_tab"></param>
    public void LoadTab(Tab p_tab)

    /// <summary>
    /// 窗口内加载多Tab,PhoneUI外套PhoneTabs,WinUI外套Tabs
    /// </summary>
    /// <param name="p_tabs"></param>
    public void LoadTabs(IList<Tab> p_tabs)
}

使用时如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var dlg = new Dlg { IsPinned = true, Title = "内嵌Tab" };
dlg.LoadTab(new TabNaviItem());
dlg.Show();

var dlg = new Dlg { Title = "内嵌多个Tab" };
dlg.LoadTabs(new List<Tab>
{ 
    new TabNaviItem(),
    new TabNaviItem(),
    new Tab { Title = "内嵌3", Content = new TextBlock { Text = "标准Tab" }},
});
dlg.Show();

/dt-docs/3%E5%AE%A2%E6%88%B7%E7%AB%AF/5tab/1.png

虚方法

针对Tab的生命周期,经常重写以下三个虚方法,OnFirstLoadedDtControl中定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/// <summary>
/// 只在第一次Loaded事件时调用,始终在OnLoadTemplate后调用
/// </summary>
protected virtual void OnFirstLoaded()

/// <summary>
/// 后退之前
/// </summary>
/// <returns>true 表允许关闭</returns>
protected virtual Task<bool> OnClosing()

/// <summary>
/// 后退完成
/// </summary>
protected virtual void OnClosed()

使用场景:OnFirstLoaded用来加载模块视图的初始数据,OnClosing切换模块视图前对修改的保存提示等。