Trigger
Trigger trong WPF được sử dụng để tự động thay đổi kiểu dáng hoặc hành vi của một kiểm soát dựa trên các điều kiện cụ thể.
Có thể coi Trigger là sự kết hợp giữa một bộ kiểm tra điều kiện và một trình thực thi hành động. Khi một điều kiện cụ thể được thỏa mãn (ví dụ: giá trị thuộc tính, giá trị ràng buộc dữ liệu), Trigger sẽ tự động thay đổi thuộc tính, kiểu dáng hoặc hành vi của một phần tử giao diện người dùng.
Trigger có thể được áp dụng cho Style, ControlTemplate và DataTemplate.
Loại Trigger và Cách Sử Dụng
Có nhiều loại Trigger khác nhau trong WPF:
- Property Trigger: Được kích hoạt khi giá trị của một thuộc tính phụ thuộc thay đổi.
- Data Trigger: Được kích hoạt khi dữ liệu ràng buộc thay đổi.
- Event Trigger: Được kích hoạt khi một sự kiện định tuyến xảy ra.
- MultiTrigger: Được kích hoạt khi nhiều điều kiện cùng được thỏa mãn.
- MultiDataTrigger: Được kích hoạt khi nhiều điều kiện ràng buộc dữ liệu cùng được thỏa mãn.
1. Property Trigger
Mục đích: Kích hoạt khi thuộc tính phụ thuộc của một kiểm soát đạt đến một giá trị cụ thể.
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Top" Background="Transparent" Text="Property Trigger: Kích hoạt khi thuộc tính phụ thuộc thay đổi." TextAlignment="Center" TextWrapping="Wrap" />
<Rectangle Width="100" Height="100">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="Green" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Fill" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</StackPanel>
2. Data Trigger
Mục đích: Kích hoạt khi dữ liệu ràng buộc thỏa mãn một điều kiện.
<StackPanel Orientation="Horizontal">
<ToggleButton Name="toggleButton" Content="Toggle Button kiểm soát thuộc tính IsEnable" />
<TextBlock Margin="10" HorizontalAlignment="Center" VerticalAlignment="Center" Text="DataTrigger: Kích hoạt khi thuộc tính IsChecked của toggleButton thay đổi.">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=toggleButton}" Value="False">
<Setter Property="Foreground" Value="Pink" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
3. Event Trigger
Mục đích: Kích hoạt khi một sự kiện của kiểm soát được kích hoạt.
Event Trigger được sử dụng để thực hiện các hành động khi một sự kiện định tuyến xảy ra, thường được sử dụng cho các hiệu ứng hoạt hình.
<Button Width="500" Height="100" Margin="10" Content="Khi nút được nhấn, độ trong suốt thay đổi">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation AutoReverse="True" Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
4. MultiTrigger và MultiDataTrigger
MultiDataTrigger: Thực hiện hành động khi nhiều điều kiện ràng buộc dữ liệu cùng thỏa mãn.
<StackPanel Margin="10" HorizontalAlignment="Center" Orientation="Horizontal">
<ToggleButton Name="toggleButton1" Content="Điều kiện 1" />
<ToggleButton Name="toggleButton2" Content="Điều kiện 2" />
<TextBlock Margin="10" HorizontalAlignment="Center" VerticalAlignment="Center" Text="Khi cả toggleButton1 và toggleButton2 đều là True">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsChecked, ElementName=toggleButton1}" Value="True" />
<Condition Binding="{Binding Path=IsChecked, ElementName=toggleButton2}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Foreground" Value="Purple" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
MultiTrigger: Thực hiện hành động khi nhiều điều kiện cùng thỏa mãn.
<Button Width="300" Height="50" Margin="10" Content="Khi chuột di chuyển vào và nhấn giữ, nền nút chuyển sang màu xanh đậm" OverridesDefaultStyle="True">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="Border" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsPressed" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="Border" Property="Background" Value="DarkBlue" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
Behavior
Behavior là một loại logic hành vi có thể tái sử dụng, được gắn vào bất kỳ kiểm soát nào để thay đổi hành vi của nó một cách động tại thời điểm chạy mà không cần sửa đổi mã của kiểm soát hoặc tạo kiểm soát tùy chỉnh, giúp việc lập trình trở nên gọn gàng và mô-đun hóa hơn.
Sử dụng Behavior
- Khi muốn đóng gói các logic tương tác phức tạp.
- Khi muốn tăng tính tái sử dụng.
- Khi kết hợp với mô hình MVVM.
Tạo Behavior Tùy Chỉnh
Bước 1: Thêm thư viện cần thiết thông qua NuGet.
Bước 2: Tạo lớp kế thừa từ System.Windows.Interactivity.Behavior<T>.
Bước 3: Định nghĩa các thuộc tính phụ thuộc cần thiết.
Bước 4: Ghi đè các phương thức OnAttached và OnDetaching.
Ví dụ về Behavior để thực hiện việc phóng to bằng bánh xe chuột:
public class ZoomBehavior : Behavior<FrameworkElement>
{
private ScaleTransform _scaleTransform = new ScaleTransform();
private Point _origin;
private Point _start;
public static readonly DependencyProperty ScaleFactorProperty =
DependencyProperty.Register("ScaleFactor", typeof(double), typeof(ZoomBehavior), new PropertyMetadata(1.0));
public double ScaleFactor
{
get { return (double)GetValue(ScaleFactorProperty); }
set { SetValue(ScaleFactorProperty, value); }
}
public static readonly DependencyProperty MinScaleProperty =
DependencyProperty.Register("MinScale", typeof(double), typeof(ZoomBehavior), new PropertyMetadata(0.1));
public double MinScale
{
get { return (double)GetValue(MinScaleProperty); }
set { SetValue(MinScaleProperty, value); }
}
public static readonly DependencyProperty MaxScaleProperty =
DependencyProperty.Register("MaxScale", typeof(double), typeof(ZoomBehavior), new PropertyMetadata(10.0));
public double MaxScale
{
get { return (double)GetValue(MaxScaleProperty); }
set { SetValue(MaxScaleProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
var transformGroup = new TransformGroup();
transformGroup.Children.Add(_scaleTransform);
AssociatedObject.RenderTransform = transformGroup;
AssociatedObject.MouseWheel += OnMouseWheel;
AssociatedObject.MouseLeftButtonDown += OnMouseLeftButtonDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.MouseWheel -= OnMouseWheel;
AssociatedObject.MouseLeftButtonDown -= OnMouseLeftButtonDown;
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_start = e.GetPosition(AssociatedObject);
_origin = new Point(_scaleTransform.ScaleX, _scaleTransform.ScaleY);
AssociatedObject.CaptureMouse();
}
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
double zoom = e.Delta > 0 ? ScaleFactor : -ScaleFactor;
double newScaleX = _scaleTransform.ScaleX + zoom;
double newScaleY = _scaleTransform.ScaleY + zoom;
if (newScaleX >= MinScale && newScaleX <= MaxScale && newScaleY >= MinScale && newScaleY <= MaxScale)
{
_scaleTransform.ScaleX = newScaleX;
_scaleTransform.ScaleY = newScaleY;
}
}
}
Sử dụng Behavior trong XAML:
<Window x:Class="BehaviorDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:local="clr-namespace:BehaviorDemo" Title="MainWindow" Width="800" Height="450">
<StackPanel Orientation="Vertical">
<Border Width="100" Height="100" Padding="5" Background="LightGray">
<i:Interaction.Behaviors>
<local:ZoomBehavior MaxScale="3.0" MinScale="0.5" ScaleFactor="0.1" />
</i:Interaction.Behaviors>
</Border>
</StackPanel>
</Window>
Tương Quan và Sự Khác Biệt giữa Trigger và Behavior
Trigger và Behavior đều được sử dụng để tăng cường khả năng tương tác và chức năng của các kiểm soát, nhưng chúng tập trung vào các khía cạnh khác nhau.
Trigger chủ yếu tập trung vào việc thay đổi kiểu dáng dựa trên thuộc tính hoặc sự kiện, trong khi Behavior cung cấp các chức năng tương tác phong phú hơn.
Ngoài ra, Trigger thường được sử dụng trực tiếp trong XAML, trong khi Behavior yêu cầu sử dụng thư viện Interactions và kết hợp cả mã và XAML.
Behavior cung cấp khả năng mở rộng mạnh mẽ hơn, cho phép tạo các Behavior tùy chỉnh, trong khi Trigger cũng có một mức độ mở rộng nhất định nhưng hạn chế hơn khi xử lý các logic phức tạp.
Học Từ Mã Nguồn Behavior Về Việc Phát Hiện Sự Kiện và Thực Thi Lệnh
Mã nguồn Behavior sử dụng kỹ thuật đăng ký và lấy tên sự kiện và lệnh thông qua các thuộc tính phụ thuộc, sau đó sử dụng phản chiếu để lấy sự kiện và thêm trình xử lý sự kiện vào sự kiện đó.
private static void OnEventNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UIElement element)
{
string eventName = e.NewValue as string;
if (!string.IsNullOrEmpty(eventName))
{
EventInfo eventInfo = element.GetType().GetEvent(eventName);
if (eventInfo != null && IsValidEvent(eventInfo))
{
MethodInfo methodInfo = typeof(EventBehavior).GetMethod("OnEventTriggered", BindingFlags.NonPublic | BindingFlags.Static);
Delegate eventHandler = Delegate.CreateDelegate(eventInfo.EventHandlerType, methodInfo);
eventInfo.AddEventHandler(element, eventHandler);
}
}
}
}
private static bool IsValidEvent(EventInfo eventInfo)
{
Type eventHandlerType = eventInfo.EventHandlerType;
if (typeof(Delegate).IsAssignableFrom(eventHandlerType))
{
MethodInfo invokeMethod = eventHandlerType.GetMethod("Invoke");
ParameterInfo[] parameters = invokeMethod.GetParameters();
return parameters.Length == 2 && typeof(object).IsAssignableFrom(parameters[0].ParameterType) && typeof(EventArgs).IsAssignableFrom(parameters[1].ParameterType);
}
return false;
}
private static void OnEventTriggered(object sender, EventArgs e)
{
if (sender is DependencyObject dependencyObject)
{
ICommand command = GetCommand(dependencyObject);
if (command != null && command.CanExecute(null))
{
command.Execute(null);
}
}
}