TabControl là loại control cho phép chứa các thành phần trong nhiều thẻ. Mỗi thẻ (hay TabItem) bao gồm hai phần chính là Header và Content. Thông thường, phần Header chính là thứ bạn cần quan tâm khi muốn thay đổi giao diện của TabControl.
Giới thiệu
TabItem là một ContentControl nên bạn có thể thêm bất kì đối tượng vào thuộc tính Content của control này. Việc sử dụng một TabControl đơn giản chỉ là thêm các TabItem cùng với hai thuộc tính Header và Content của chúng.
Một TabControl bao gồm hai phần chính là:
– TabPanel: chứa header của các TabItem
– SelectedContent: vùng hiển thị nội dung của TabItem được chọn.
Khi tạo control template cho TabControl, bạn cần xác định vị trí của TabPanel bên trong. Có thể đặt nhiều TabPanel nhưng bạn cần chỉ ra TabPanel nào sẽ chứa các header của TabItem thông qua property TabPanel.IsItemsHost=True. Cùng với TabItem, bạn cần thêm một ContentPresenter để hiển thị nội dung của property SelectedContent.
… <Window.Resources> <Style TargetType="{x:Type TabControl}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="TabControl"> <DockPanel> <TabPanel Height="20" DockPanel.Dock="Top" Background="BlanchedAlmond"/> <TabPanel Height="20" DockPanel.Dock="Top" Background="Red" IsItemsHost="True"/> <Grid Background="AliceBlue"> <ContentPresenter ContentSource="SelectedContent"/> </Grid> </DockPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> <Grid> <TabControl> <TabItem Header="Lemon" /> <TabItem Header="Orange"/> </TabControl> </Grid> …
Ví dụ trên tạo ra một TabControl với hai TabPanel như sau:
Tùy biến TabItem
Tạo template cho TabItem cũng tương tự như tạo cho TabControl, nhưng bạn cần gán ContentPresenter.ContentSource=”Header”:
<ControlTemplate TargetType="{x:Type TabItem}"> <Grid> <Border x:Name="Border1" Background="{StaticResource LightBackgroundBrush}" BorderBrush="Blue" BorderThickness="1" CornerRadius="5,5,0,0" > <ContentPresenter ContentSource="Header" Margin="10,2,10,2"/> </Border> </Grid> </ControlTemplate>
Tuy nhiên việc thiết kế này chưa hoàn chỉnh nên bạn không thể nhận ra sự khác biệt khi một TabItem được chọn hay không. Để làm được điều này, bạn cần sử dụng các trigger để thay đổi một số giá trị của Border mà tôi đã đặt tên là Border1 trong đoạn mã trên.
Ta sẽ tạo hai trigger cho hai giá trị của property TabItem.IsSelected, tương ứng với trạng thái được chọn hay không của các TabItem.
Các property của Border cần sửa trong ví dụ gồm có:
– Margin: để thay đổi kích thước header (của TabItem).
– BorderThickness: bỏ đường viền phía dưới header đang được chọn.
– Background: thay đổi màu nền của các header.
Ngoài ra bạn cần sửa một property là Panel.ZIndex của TabItem:
– Panel.Zindex: là một attached property, giá trị càng lớn thì TabItem sẽ được đưa lên phía trên các TabItem khác. Như vậy, ta cần đặt giá trị này của TabItem được chọn lớn hơn các TabItem khác.
Sau đó, tôi tạo một Style để áp dụng cho tất cả TabItem:
… <Window.Resources> <!--LinearGradientBrush--> <LinearGradientBrush x:Key="LightBackgroundBrush" StartPoint="0,0" EndPoint="0,1"> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.4" Color="LightGoldenrodYellow"/> <GradientStop Offset="0.6" Color="BurlyWood"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush> <!-- TabItem Style--> <Style TargetType="{x:Type TabItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TabItem}"> <Grid> <Border x:Name="Border1" BorderBrush="Blue" CornerRadius="5,5,0,0" > <ContentPresenter ContentSource="Header" Margin="10,2,10,2"/> </Border> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsSelected" Value="False"> <Setter TargetName="Border1" Property="Margin" Value="0,5,0,0"/> <Setter TargetName="Border1" Property="BorderThickness" Value="1" /> <Setter TargetName="Border1" Property="Background" Value="LightGray" /> <Setter Property="Panel.ZIndex" Value="1" /> </Trigger> <Trigger Property="IsSelected" Value="True"> <Setter TargetName="Border1" Property="Margin" Value="-5,0,-5,0"/> <Setter TargetName="Border1" Property="BorderThickness" Value="1,1,1,0" /> <Setter TargetName="Border1" Property="Background" Value="{StaticResource LightBackgroundBrush}" /> <Setter Property="Panel.ZIndex" Value="2" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> …
Kết quả:
Tạo Closeable TabItem
Một số ứng dụng yêu cầu các TabControl có thể loại bớt các TabItem, bằng cách click Button đóng trên header của mỗi TabItem.
Để tạo Button đóng cho mỗi TabItem, bạn cần thiết kế trong control template. Các Button đóng này sẽ được xử lý sự kiện Click để tìm ra TabItem chứa nó và loại TabItem đó ra khỏi TabControl.
XAML:
… <Window.Resources> <!--LinearGradientBrush--> <LinearGradientBrush x:Key="LightBackgroundBrush" StartPoint="0,0" EndPoint="0,1"> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.4" Color="LightGoldenrodYellow"/> <GradientStop Offset="0.6" Color="BurlyWood"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush> <!-- TabItem Style--> <Style TargetType="{x:Type TabItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TabItem}"> <Border Background="{StaticResource LightBackgroundBrush}" BorderBrush="Blue" CornerRadius="5,5,0,0" BorderThickness="1"> <StackPanel Orientation="Horizontal" Margin="2"> <ContentPresenter ContentSource="Header" Margin="10,2,10,2"/> <Button Background="Transparent" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" Height="20" Width="20" Content="X" Click="tabItemCloseButton_Click"/> </StackPanel> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> <Grid> <TabControl Name="tabControl1"> <TabItem Header="Lemon"> <Image Height="200" Width="200" Source="Images\Lemon.jpg"/> </TabItem> <TabItem Header="Orange"> <Image Height="200" Width="200" Source="Images\Orange.jpg"/> </TabItem> <TabItem Header="Polemo"/> </TabControl> </Grid> ...
Phương pháp lấy được TabItem này dựa vào phương thức VisualTreeHelper.GetParent(). Phương thức này sẽ lấy thành phần cha của một DependencyObject trên VisualTree. Bằng cách sử dụng vòng lặp cho đến khi duyệt tới TabItem, ta sẽ có được đối tượng TabItem chứa Button được click.
C# code-behind:
// … private void tabItemCloseButton_Click(object sender, RoutedEventArgs e) { DependencyObject obj = sender as DependencyObject; while (!(obj is TabItem)) { obj = VisualTreeHelper.GetParent(obj); } tabControl1.Items.Remove(obj); } // …
https://yinyangit.wordpress.com
Bài liên quan
Cám ơn anh đã chia sẻ nhiều kiến thức rất bổ ích. Nếu có thể hy vọng anh sẽ viết về mô hình MVVM cho mọi người cùng tìm hiểu.
Mình cũng đang dự định viết về mô hình này, yêu cầu của bạn sẽ được đáp ứng trong khoảng vài bài viết nữa.
dạ!hy vọng sớm được đọc bài viết viết của anh. Chúc anh 1 ngày làm việc hiệu quả
Cảm ơn các bài viết hữu ích của bạn. Chúc bạn sẽ thành công hơn nữa trong công việc, cuộc sống cũng như trong việc share kiến thức đến mọi người !
Cảm ơn 2 bạn.
Em chào anh!
Cho em hỏi:
Ở cái phần “Tạo Closeable TabItem”. Trên đó a khai báo Style ở ngay trong file MainWindows luôn, nên có thể tham chiếu tới cái hàm “tabItemCloseButton_Click”… Nếu như em khái báo cái Style đó riêng ở một file ResourceDictionary khác, thì làm sao gọi đc hàm “tabItemCloseButton_Click” ạ. Em có thử mà ko đc.
Em mới học WPF nên có gì a thông cảm!
Cám ơn các bài viết hữu ích của anh! ^^
Bạn có thể đặt một class code-behind cho Resource Dictionary tương tự như cho một tài liệu XAML của Window, UserControl,… Trong code behind bạn sẽ viết code xử lý đóng tab đó nhưng cần phải duyệt vì nó không thể truy xuất trực tiếp đến TabControl.
Class MyResourceEvents:
Em chào anh!
E mới tìm hiểu WPF, có tham khảo cách tạo Closeable Tabitems ở trên
Em có làm theo hướng dẫn của anh nhưng mà e triển khai bằng ngôn ngữ Vb.net. Em viết code-behind như sau:
Private Sub tabItemCloseButton_Click(ByVal sender As Object, ByVal e As RoutedEvent)
Dim obj As DependencyObject = TryCast(sender, DependencyObject)
While Not (TypeOf obj Is TabItem)
obj = VisualTreeHelper.GetParent(obj)
End While
tabControl1.Items.Remove(obj)
End Sub
Nhưng khi e lại nhận được thông báo lỗi như sau:
Error 1 Method ‘Private Sub tabItemCloseButton_Click(sender As Object, e As System.Windows.RoutedEvent)’ does not have a signature compatible with delegate ‘Delegate Sub RoutedEventHandler(sender As Object, e As System.Windows.RoutedEventArgs)’
Lỗi này hiển thị trên file MainWindows.g.vb
Vậy anh có thể giúp e khắc phục lỗi này được không ạ.
Em cảm ơn
Bạn sửa “ByVal e As RoutedEvent” thành “ByVal e As RoutedEventArgs” xem.
haha. Ok Thanks bác nhé…
Dạ thưa anh!
Em có một vấn đề về cái Closetabable TabItems. Anh có thể hướng dẫn giúp em được không ạ.
Hiện tại em đã sử dụng được Closetabale được rồi nhưng giờ em có vấn đề là e gọi một cái Tabitems để nó hiển thị lên thông qua sự kiện nào đó của một control và khi em tắt cái tabitems đó đi và gọi lại thì không thể nào được ạ…
Rất mong nhân được sự giúp đỡ của a
Để thêm một TabItem vào TabControl rất đơn giản, bạn có thể làm như ví dụ sau:
TabItem newTab = new TabItem();
newTab.Content = “abc”;
newTab.Header = “New Tab”;
tabControl1.Items.Add(newTab);
Để add 1 tab đã được remove thì trước khi remove bạn lưu tab đó vào một biến nào đó rồi Add như cách trên.
ok, Cảm ơn anh nhiều nhé…
Cái này thực sự trước khi remove cũng không cần pải lưu vào biến nào hết mà ta cứ thế Add vào là được thôi anh ak
Tại vì bạn đã đặt sẵn tên cho nó, biến chỉ có mục đích giúp ta tham chiếu tới nó thôi.
Cảm ơn bạn rất nhiều vì những chia sẻ của bạn. Hiện nay mình cũng đang làm việc với WPF. Một vấn đề mình gặp phải là viết template cho control, mình thấy việc gõ code xaml để design một control mất rất nhiều thời gian và cũng ko trực quan. Mình có tìm hiểu và biết Microsoft Expression Blend có hỗ trợ kéo thả trong việc xây dựng temp cho control. Xin hỏi Yin Yang có sử dụng blend ko? Nếu có thì mong bạn có những chia sẻ về vấn đề này. Cảm ơn bạn trước 😀
Mình ko sử dụng Blend cũng như ko chuyên WPF, chỉ là học cho biết thôi.