WPF TabControl 模板
我是 WPF 新用户,我正在尝试了解模板。特别是我正在尝试模板化 TabControl。
我对 TabControl 绑定到数据时实际生成的控件感到困惑。我需要的是每个选项卡的内容部分都有一个单独的控件副本。我发现,似乎创建了一组控件,并且当用户从一个选项卡单击到另一个选项卡时,只有绑定的数据发生变化。
项目描述:
示例项目由带有 TabControl 的单个视图组成。 TabControl 的内容模板中是一个ListBox。视图绑定到 TabBindingViewModel 类的对象。该类有一个名为 Tabs 的属性,它是 TabViewModel 对象的 ObservableCollection。 TabViewModel 类有两个属性:TabHeader 和 Guids。 TabViewModel.TabHeader 是一个字符串,其中包含将出现在选项卡上的文本,TabViewModel.Guids 是一个 ObservableCollection 字符串。
绑定后,TabBindingViewModel.Tabs 集合中的每个 TabViewModel 应在 TabControl 对象中生成一个选项卡,该选项卡的标头为 TabViewModel.TabHeader 属性。每个选项卡的内容应该是用 TabViewModel.Guids 集合中的字符串集合填充的 ListBox。
问题
这一切似乎都渲染/绑定得很好,但是如果您更改列表框的状态(例如滚动或选择一个项目),然后更改选项卡,则该状态似乎会转移到您刚刚选择的新选项卡上的列表框。这让我假设 ListBox 只有一个实例(而不是 5 个单独的实例),并且当您更改选项卡时,数据是唯一发生更改的内容。
在另一种情况下,我没有将集合绑定到模板,而是在 xaml 中使用其自己的 ListBox 显式定义每个 TabItem 对象。这次,当我从一个选项卡单击到另一个选项卡时,每个列表框都会保持其自己的状态。
我的假设正确吗?如果是这样,我如何实现第二种情况中描述的结果,同时仍然利用模板? (这是一个简化的示例。我的现实世界内容要复杂得多。)
预先感谢您的任何帮助!
下面列出了我的示例的源代码。
查看课程
1. TabBinding
<Window x:Class="TabBindingQuestion.View.TabBinding"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TabBindingQuestion.ViewModel"
Title="TabTemplateView"
Height="344" Width="618">
<Window.Resources>
<vm:TabBindingViewModel x:Key="Data" />
</Window.Resources>
<Grid DataContext="{StaticResource Data}">
<TabControl Width="Auto"
Height="Auto"
ItemsSource="{Binding Tabs}"
IsSynchronizedWithCurrentItem="True">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding TabHeader}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ListBox Width="Auto"
Height="Auto"
ItemsSource="{Binding Guids}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
</Window>
ViewModel 类
2. ViewModelBase
using System.ComponentModel;
namespace TabBindingQuestion.ViewModel
{
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}
}
}
3. TabBindingViewModel
using System.Collections.ObjectModel;
namespace TabBindingQuestion.ViewModel
{
class TabBindingViewModel : ViewModelBase
{
#region Members
private ObservableCollection<TabViewModel> _Tabs;
public ObservableCollection<TabViewModel> Tabs
{
get { return _Tabs; }
set
{
_Tabs = value;
OnPropertyChanged(this, "Tabs");
}
}
#endregion
#region Constructor
public TabBindingViewModel()
{
var tabs = new ObservableCollection<TabViewModel>();
for (int i = 1; i <= 5; i++)
{
tabs.Add(new TabViewModel() { TabHeader = "Tab " + i.ToString() });
}
Tabs = tabs;
}
#endregion
}
}
4. TabViewModel
using System;
using System.Collections.ObjectModel;
namespace TabBindingQuestion.ViewModel
{
class TabViewModel : ViewModelBase
{
#region Members
private string _TabHeader;
public string TabHeader
{
get { return _TabHeader; }
set
{
_TabHeader = value;
OnPropertyChanged(this, "TabHeader");
}
}
private ObservableCollection<string> _Guids;
public ObservableCollection<string> Guids
{
get { return _Guids; }
set
{
_Guids = value;
OnPropertyChanged(this, "Guids");
}
}
#endregion
#region Constructors
public TabViewModel()
{
var guids = new ObservableCollection<string>();
for (int i = 1; i < 100; i++)
{
guids.Add(Guid.NewGuid().ToString());
}
Guids = guids;
}
#endregion
}
}
I am a new WPF user and I am trying to wrap my head around templating. In particular I am trying to template a TabControl.
I am confused by the controls that actually get generated when the TabControl is bound to data. What I need is for each tab to have a separate copy of the controls in it's content section. What I have found is that it appears that a single set of controls are created, and when the user clicks from tab to tab, just the bound data changes.
Project Description:
The example project consists of a single view with a TabControl. In the content template of the TabControl is a ListBox. The view gets bound to an object of the TabBindingViewModel class. This class has one property called Tabs which is an ObservableCollection of TabViewModel objects. The TabViewModel class has two properties: TabHeader and Guids. TabViewModel.TabHeader is a string that contains the text that will appear on the tab, and TabViewModel.Guids is an ObservableCollection of strings.
When bound, each TabViewModel in the TabBindingViewModel.Tabs collection should generate a tab in the TabControl object with the header of the tab being the TabViewModel.TabHeader property. The content of each tab should be the ListBox populated with the collection of strings in the TabViewModel.Guids collection.
Problem
This all appears to render/bind just fine, however if you change the state of a ListBox (for example scrolling or selecting an item) and then change tabs, it appears that the state carries over to the ListBox on the new tab that you just selected. This makes me assume that there is only one instance of the ListBox (instead of 5 separate instances) and when you change tabs, the data is the only thing that changes.
In another scenario, instead of binding a collection to a template, I explicitly define each TabItem object with it's own ListBox in the xaml. This time, when I click from tab to tab, each ListBox maintains it's own state.
Am I correct in my assumptions? And if so, how do I achieve the result described in the second scenario while still taking advantage of templating? (This is a simplified example. My real world content is much more complex.)
Thanks in advance for any help!
The source code for my example is listed below.
View Classes
1. TabBinding
<Window x:Class="TabBindingQuestion.View.TabBinding"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TabBindingQuestion.ViewModel"
Title="TabTemplateView"
Height="344" Width="618">
<Window.Resources>
<vm:TabBindingViewModel x:Key="Data" />
</Window.Resources>
<Grid DataContext="{StaticResource Data}">
<TabControl Width="Auto"
Height="Auto"
ItemsSource="{Binding Tabs}"
IsSynchronizedWithCurrentItem="True">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding TabHeader}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ListBox Width="Auto"
Height="Auto"
ItemsSource="{Binding Guids}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
</Window>
ViewModel Classes
2. ViewModelBase
using System.ComponentModel;
namespace TabBindingQuestion.ViewModel
{
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}
}
}
3. TabBindingViewModel
using System.Collections.ObjectModel;
namespace TabBindingQuestion.ViewModel
{
class TabBindingViewModel : ViewModelBase
{
#region Members
private ObservableCollection<TabViewModel> _Tabs;
public ObservableCollection<TabViewModel> Tabs
{
get { return _Tabs; }
set
{
_Tabs = value;
OnPropertyChanged(this, "Tabs");
}
}
#endregion
#region Constructor
public TabBindingViewModel()
{
var tabs = new ObservableCollection<TabViewModel>();
for (int i = 1; i <= 5; i++)
{
tabs.Add(new TabViewModel() { TabHeader = "Tab " + i.ToString() });
}
Tabs = tabs;
}
#endregion
}
}
4. TabViewModel
using System;
using System.Collections.ObjectModel;
namespace TabBindingQuestion.ViewModel
{
class TabViewModel : ViewModelBase
{
#region Members
private string _TabHeader;
public string TabHeader
{
get { return _TabHeader; }
set
{
_TabHeader = value;
OnPropertyChanged(this, "TabHeader");
}
}
private ObservableCollection<string> _Guids;
public ObservableCollection<string> Guids
{
get { return _Guids; }
set
{
_Guids = value;
OnPropertyChanged(this, "Guids");
}
}
#endregion
#region Constructors
public TabViewModel()
{
var guids = new ObservableCollection<string>();
for (int i = 1; i < 100; i++)
{
guids.Add(Guid.NewGuid().ToString());
}
Guids = guids;
}
#endregion
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
是的,如果切换选项卡时控件相同,TabControl 会重复使用控件。它还根据需要卸载/重新加载控件,这会导致未绑定的控件被重置。例如,如果 Tab1 有一个列表框,其中选择了 ItemA,并且您选择了 ItemB 并将选项卡切换到没有列表框的选项卡,则当您返回到 Tab1 时,ItemA 会再次被选中。
最好的办法是将 UI 属性绑定到后面代码中的某些内容。例如,您的 TabControl 可能包含 TabItemViewModel 列表,并且每个 ViewModel 应包含表示 UI 状态的属性,例如 ListBox.SelectedItems 或 CheckBox.IsChecked。
Yes the TabControl re-uses controls if they are the same when switching tabs. It also Unloads/Reloads controls as needed which causes unbound controls to be reset. For example, if Tab1 has a ListBox with the ItemA selected and you select ItemB and switch tabs to one without the listbox, when you go back to Tab1 ItemA is selected again.
Best thing to do is bind your UI properties to something in the code behind. For example, your TabControl might contain a list of TabItemViewModel and each ViewModel should contain properties representing the state of your UI, such as ListBox.SelectedItems or CheckBox.IsChecked.