本文介绍使用mvvm模式操作TabView的标签添加和关闭,视图有两种样式,如下:
选项卡1的视图
选项卡2的视图
以下1.和2.为标准代码
1.定义对应于TabViewItem的视图模式
public class TabItemViewModel : INotifyPropertyChanged
{
#region 构造函数
public TabItemViewModel()
{
}
public TabItemViewModel(string headerName)
{
_Header = headerName;
}
public TabItemViewModel(string headerName,object data)
{
_Header = headerName;
_Data = data;
}
public TabItemViewModel(string headerName, object data, string tooltip)
{
_Header = headerName;
_Data = data;
_ToolTip = tooltip;
}
#endregion
#region 公共属性
[XmlIgnore]
public TabMainViewModel Parent { get; set; }
bool _DataChanged;
/// <summary>
/// 数据已改变
/// </summary>
[XmlAttribute]
public bool DataChanged
{
get
{
return _DataChanged;
}
set
{
if (_DataChanged != value)
{
_DataChanged = value;
this.OnPropertyChanged();
}
}
}
string _Header;
/// <summary>
/// Tab标头内容
/// </summary>
[XmlAttribute]
public string Header
{
get
{
return _Header;
}
set
{
if (_Header != value)
{
_Header = value;
this.OnPropertyChanged();
}
}
}
string _ToolTip;
/// <summary>
/// Tab标头提示文本
/// </summary>
[XmlAttribute]
public string ToolTip
{
get
{
return _ToolTip;
}
set
{
if (_ToolTip != value)
{
if (value != null && value.Trim() == "")
_ToolTip = null;
else
_ToolTip = value;
this.OnPropertyChanged();
}
}
}
bool _IsSelected = false;
/// <summary>
/// 设置Tab项是否被选择
/// </summary>
[XmlAttribute]
public bool IsSelected
{
get
{
return _IsSelected;
}
set
{
if (_IsSelected != value)
{
_IsSelected = value;
this.OnPropertyChanged();
}
}
}
object _Icon;
/// <summary>
/// Tab项图标
/// </summary>
[XmlAttribute]
public object Icon
{
get
{
return _Icon;
}
set
{
if (_Icon != value)
{
_Icon = value;
this.OnPropertyChanged();
}
}
}
object _Data;
/// <summary>
/// 用于绑定的数据内容
/// </summary>
[XmlAttribute]
public object Data
{
get
{
return _Data;
}
set
{
if (_Data != value)
{
_Data = value;
this.OnPropertyChanged();
}
}
}
#endregion
#region 属性通知
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion
}//
2.定义对应于TabView的视图模式
public class TabMainViewModel : INotifyPropertyChanged
{
#region 构造函数
public TabMainViewModel()
{
}
public TabMainViewModel(Func<TabItemViewModel, bool> preClose)
{
PreClose=preClose;
}
#endregion
#region 属性定义
ObservableCollection<TabItemViewModel> _Items;
/// <summary>
/// 项集合属性
/// </summary>
public ObservableCollection<TabItemViewModel> Items
{
get
{
if (_Items == null)
{
_Items = new ObservableCollection<TabItemViewModel>();
}
return _Items;
}
}
#endregion
#region 关闭方法,仅用于winui3的tabview
public Func<TabItemViewModel, bool> PreClose { get; set; }
public void TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args)
{
var tabItem = args.Tab.DataContext as TabItemViewModel;
if (PreClose!=null && PreClose(tabItem)) //确定关闭
{
Items.Remove(tabItem);
}
//sender.TabItems.Remove(args.Tab);
}
#endregion
#region 属性通知
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion
}//
3.定义内容视图的数据模型(用于绑定TabViewItem中的Content)
internal class TabItem1ViewModel
{
public TabItem1ViewModel()
{
Name = "Item1";
Description = "Desc1";
Items = new List<string>()
{
"str1","str2","str3","str4","str5","str6","str7","str8","str9"
};
}
public string Name { get; set; }
public string Description { get; set; }
public List<string> Items { get; set; }
}
internal class TabItem2ViewModel
{
public TabItem2ViewModel()
{
Name = "Item2";
Description = "Desc2";
}
public string Name { get; set; }
public string Description { get; set; }
}
4.定义内容视图(对应TabViewItem中的ContentTemplate)
<UserControl
x:Class="AppTest.View.TabItem1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AppTest.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Name}"/>
<TextBox Text="{Binding Description}" Grid.Row="1"/>
<ListBox ItemsSource="{Binding Items}" Grid.Row="2"/>
</Grid>
</UserControl>
<UserControl
x:Class="AppTest.View.TabItem2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AppTest.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Name}"/>
<TextBox Text="{Binding Description}" Grid.Row="1"/>
</Grid>
</UserControl>
5.定义两个视图的数据模板
在数据模板的定义中,不同的是TabViewItem.ContentTemplate,内部分别嵌套上面定义的两个UserControl,通过Content="{Binding Data}",将上面定义的TabItem1ViewModel/ TabItem2ViewModel传递到UserControl。
<DataTemplate x:Key="TabItem1ViewModel">
<TabViewItem Header="{Binding Header}"
Content="{Binding Data}"
IsSelected="{Binding IsSelected,Mode=TwoWay}"
ToolTipService.ToolTip="{Binding ToolTip}">
<TabViewItem.IconSource>
<FontIconSource Glyph="{Binding Icon}" />
</TabViewItem.IconSource>
<TabViewItem.ContentTemplate>
<DataTemplate>
<view:TabItem1/>
</DataTemplate>
</TabViewItem.ContentTemplate>
</TabViewItem>
</DataTemplate>
<DataTemplate x:Key="TabItem2ViewModel">
<TabViewItem Header="{Binding Header}"
Content="{Binding Data}"
IsSelected="{Binding IsSelected,Mode=TwoWay}"
ToolTipService.ToolTip="{Binding ToolTip}">
<TabViewItem.IconSource>
<FontIconSource Glyph="{Binding Icon}" />
</TabViewItem.IconSource>
<TabViewItem.ContentTemplate>
<DataTemplate>
<view:TabItem2/>
</DataTemplate>
</TabViewItem.ContentTemplate>
</TabViewItem>
</DataTemplate>
上面xaml代码有很多的重复,譬如Header/ Content/ IsSelected的绑定都是相同的,如果使用的是WPF,至少有两个方式解决问题,第一种根本就不需要使用上面的这种方式定义数据模板,只需要定义不带x:Key的数据模板,类似如下,即可实现视图模式与视图的关联:
<DataTemplate DataType="{ x: Type WpfCode:XAMLCodeViewModel}">
<WpfCode:XAMLCodeView />
</DataTemplate>
可惜WinUI不容许,第二种就是定义TabViewItem的样式,不用修改模板,仅是属性的绑定,然后在上面指定样式资源即可,但在这里WinUI不行,即使定义了不修改模板的样式也不行。
6.定义模板选择器
<view:TabTemplateSelector x:Key="TabItemTemplateSelector"
TabItem1Template="{StaticResource TabItem1ViewModel}"
TabItem2Template="{StaticResource TabItem2ViewModel}" />
public class TabTemplateSelector : DataTemplateSelector
{
public DataTemplate TabItem1Template { get; set; }
public DataTemplate TabItem2Template { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
var tabitem = (TabItemViewModel)item;
return tabitem.Data is TabItem1ViewModel ? TabItem1Template : TabItem2Template;
}
}
7.TabView的xaml代码
<TabView TabItemsSource="{x:Bind TabViewData.Items, Mode=OneWay}"
TabItemTemplateSelector="{StaticResource TabItemTemplateSelector}"
TabCloseRequested="{x:Bind TabViewData.TabCloseRequested}"
>
</TabView>
8.后台测试代码
TabMainViewModel TabViewData = new TabMainViewModel();
void InitTabData()
{
TabItemViewModel item1 = new TabItemViewModel("项1", new TabItem1ViewModel());
TabItemViewModel item2 = new TabItemViewModel("项2", new TabItem2ViewModel());
item1.Icon= ((char)0xE75A).ToString();
item2.Icon = ((char)0xE102).ToString();
TabViewData.Items.Add(item1);
TabViewData.Items.Add(item2);
TabViewData.PreClose = PreClose;
}
bool PreClose(TabItemViewModel tabitem)
{
//处理是否保存/是否关闭之类的功能
return true;
}
第一次使用WinUI,个人感觉WinUI在xaml语法上是WPF的阉割版,除了添加了一个x:Bind类型的绑定,但这也是在标准Binding的一个扩展。
WinUI3确实做得很漂亮,效率也很高。如果个人想自定义样式,可以下载winui3组件的源码,然后找到相应的文件夹里面的组件xaml打开,不用打开整个项目,因为其后台代码使用的是C++,这样基本上可以看到完整的xaml源码,对于自定义样式或者数据绑定有很大的帮助。
WinUI3自定义了很多文字图标,上面的TabItem中就用到了,查阅图标对应的字符串可以在微软网站上找到: