WPF – Tùy biến TabControl

WPF - TabControl - Custom TabItemTabControl 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.

WPF TabControl

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:

WPF - Custom TabControl

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ả:

WPF - TabControl - Custom TabItem

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);
}
// …

WPF - TabControl - Closeable TabItems
https://yinyangit.wordpress.com

Bài liên quan

Advertisements

16 thoughts on “WPF – Tùy biến TabControl

  1. 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! ^^

    Phản hồi
  2. 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.

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        x:Class="WpfApplication1.MyResourceEvents">
     ...
    

    Class MyResourceEvents:

    partial class MyResourceEvents
    {
    
        private void tabItemCloseButton_Click(object sender, RoutedEventArgs e)
        {
            DependencyObject obj = sender as DependencyObject;
    
            while (!(obj is TabItem))
            {
                obj = VisualTreeHelper.GetParent(obj);
            }
            DependencyObject tabControl = obj;
            while (!(tabControl is TabControl))
            {
                tabControl = VisualTreeHelper.GetParent(tabControl);
            }
            ((TabControl)tabControl).Items.Remove(obj);
        }
    }
    
    Phản hồi
  3. 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

    Phản hồi
  4. 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

    Phản hồi
  5. Để 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.

    Phản hồi
  6. 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 😀

    Phản hồi

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất / Thay đổi )

Connecting to %s