WPF: Tạo Control Hiển Thị Văn Bản Với Chiều Cao Tự Động Điều Chỉnh

Yêu cầu: Trong một ứng dụng cập nhật phiên bản, cần hiển thị thông tin dạng văn bản với chiều rộng cố định nhưng chiều cao thay đổi linh hoạt theo nội dung. Chiều cao phải nằm trong khoảng cho phép — có giá trị tối thiểu và tối đa. Khi nội dung vượt quá giới hạn chiều cao, thanh cuộn dọc sẽ xuất hiện.

Cụ thể, control cần đảm bảo:

  • Chiều cao tự động co giãn theo nội dung văn bản.
  • Không nhỏ hơn MinHeight.
  • Không lớn hơn MaxHeight.
  • Vượt quá MaxHeight thì kích hoạt thanh cuộn.

Phân tích giải pháp

Thành phần chính gồm:

  • Một Border làm khung chứa (đặt MinHeight="207", MaxHeight="503").
  • Bên trong là ScrollViewer bao quanh TextBlock để xử lý văn bản dài.
  • Chiều cao của Border cần được cập nhật động dựa trên độ cao thực tế của nội dung văn bản.

Vấn đề đặt ra: Nếu chỉ thiết lập MinHeightMaxHeight, WPF sẽ chiếm dụng toàn bộ không gian đến MaxHeight, bất kể nội dung ngắn hay dài — điều này không đáp ứng yêu cầu "tự động co giãn".

Giải pháp: Dùng Binding kết hợp ValueConverter

Ý tưởng chính: Ràng buộc (bind) thuộc tính Height của Border vào ExtentHeight của ScrollViewer — giá trị này phản ánh chiều cao thực tế của nội dung bên trong vùng cuộn.

Bước 1: Tạo Converter chuyển đổi chiều cao

Tạo lớp converter để lấy ExtentHeight từ ScrollViewer và cộng thêm khoảng đệm (padding, margin) nếu cần để đảm bảo hiển thị đầy đủ các thành phần khác trong layout.

using System;
using System.Globalization;
using System.Windows.Data;

public class DynamicHeightConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is double extentHeight)
        {
            // Cộng thêm khoảng không gian cố định dành cho tiêu đề, nút,...
            const double fixedSpace = 80; 
            return Math.Max(207, Math.Min(extentHeight + fixedSpace, 503));
        }
        return 207; // Giá trị mặc định tối thiểu
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

Bước 2: Áp dụng Binding trong XAML

Đăng ký converter trong tài nguyên cửa sổ và ràng buộc Height của Border tới ExtentHeight của ScrollViewer.

<Window x:Class="DynamicHeightApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DynamicHeightApp"
        Title="Cập Nhật Phiên Bản" Width="800" Height="500">

    <Window.Resources>
        <local:DynamicHeightConverter x:Key="HeightConverter"/>
    </Window.Resources>

    <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
        <Border x:Name="ContentBorder"
                Width="360"
                MinHeight="207"
                MaxHeight="503"
                Height="{Binding ElementName=ContentScroller, Path=ExtentHeight, Converter={StaticResource HeightConverter}}"
                Background="#F0F0F0"
                Padding="30,10,30,20"
                CornerRadius="10"
                BorderThickness="1"
                BorderBrush="#DDD">

            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="*"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>

                <!-- Tiêu đề -->
                <TextBlock Grid.Row="0"
                           Margin="0,20,0,0"
                           FontSize="20"
                           FontWeight="Bold"
                           Text="Phát hiện phiên bản mới!" />

                <!-- Mô tả ngắn -->
                <TextBlock Grid.Row="1"
                           Margin="0,16,0,8"
                           FontSize="16"
                           FontWeight="SemiBold"
                           Text="Thông tin phiên bản" />

                <!-- Nội dung cuộn -->
                <ScrollViewer Grid.Row="2"
                              x:Name="ContentScroller"
                              Margin="0,10"
                              VerticalScrollBarVisibility="Auto"
                              HorizontalScrollBarVisibility="Disabled"
                              CanContentScroll="True"
                              PanningMode="VerticalOnly">
                    <TextBlock x:Name="ContentText"
                               TextWrapping="Wrap"
                               FontSize="14"
                               Margin="0,0,10,10"
                               Text="Nội dung mô tả cập nhật rất dài... [dữ liệu mẫu]" />
                </ScrollViewer>

                <!-- Nút hành động -->
                <StackPanel Grid.Row="3"
                            Margin="0,10,0,0"
                            Orientation="Horizontal"
                            HorizontalAlignment="Right"
                            VerticalAlignment="Center">
                    <Button Content="Hoãn lại"
                            Background="#E7E7E7"
                            Foreground="#000"
                            Padding="12,6"/>
                    <Button Content="Cập nhật ngay"
                            Margin="10,0,0,0"
                            Background="#0052D9"
                            Foreground="#FFF"
                            Padding="12,6"/>
                </StackPanel>
            </Grid>
        </Border>
    </Grid>
</Window>

Giải thích hoạt động

  • ScrollViewer.ExtentHeight: Trả về chiều cao tổng thể của nội dung bên trong (không bị giới hạn bởi vùng hiển thị).
  • DynamicHeightConverter: Nhận giá trị này, cộng thêm không gian cho các thành phần cố định (tiêu đề, nút), sau đó giới hạn trong khoảng [207, 503].
  • Height của Border được cập nhật liên tục khi nội dung thay đổi — giúp giao diện co giãn mềm dẻo.

Kết quả: Với nội dung ngắn, hộp thoại co lại vừa vặn. Với nội dung dài, nó mở rộng đến giới hạn cho phép rồi kích hoạt thanh cuộn — đúng như yêu cầu ban đầu.

Thẻ: WPF XAML ScrollViewer DataBinding ValueConverter

Đăng vào ngày 2 tháng 6 lúc 22:16