Dạo vài năm gần đây mình thấy xu hướng thiết kế navigation bar trên website hay mobile app đều có dạng slider ở cạnh bênh. Dễ thấy nhất là các app của google cho mobile. Vậy nên hôm nay mình sẽ thử biến Grid panel của WPF thành một thanh navigation bar giống vậy.
Chúng ta sẽ có một thứ gì đó trông giống thế này. Để làm giống như hình dưới chúng ta có nhiều cách làm và sau đây là một cách dễ nhất.
Ý tưởng: Tôi sẽ tạo một class mới (là control mới đấy) kế thừa từ Grid. Trong class này tôi sử dụng một Timer để định thời thay đổi kích thước của chính nó nhằm tạo hiệu ứng trượt. Việc thay đổi kích thước sẽ tiến hành chỉ khi có một event xảy ra, ở đây là event IsOpenChanged (event tự tạo nhé).
Kịch bản: Khi người dùng thay đổi giá trị của property IsOpen (bằng cách binding với biến bool chẳng hạn) thì tôi sẽ nhận được một event IsOpenChanged, sau đó tôi kiểm tra nếu IsOpen là True nghĩa là sẽ phải hiện control này ra và ngược lại, kèm theo hiệu ứng trược. Lưu ý rằng IsOpen là một Dependency Property.
CustomGridPanel.cs
using System; using System.Windows; using System.Windows.Controls; namespace CustomControl { public class CustomGridPanel: Grid { private enum Action { Open, Close } #region Variables private const double DEFAULT_SPEED = 5; private System.Windows.Threading.DispatcherTimer dispatcherTimer; private double speed; private double exspectedMinWidth; private double exspectedMaxWidth; #endregion public static event PropertyChangedCallback IsOpenChanged; public bool IsOpen { get { return (bool)GetValue(IsOpenProperty); } set { SetValue(IsOpenProperty, value); } } public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register("IsOpen", typeof(bool), typeof(CustomGridPanel), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsRender, IsOpenChangedAction)); private static void IsOpenChangedAction(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (IsOpenChanged != null) { IsOpenChanged.Invoke(d, e); } } public CustomGridPanel() { IsOpenChanged += (sender, e) => { InvokeAction(IsOpen ? Action.Open : Action.Close); }; } protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); // Init variable exspectedMaxWidth = Width; exspectedMinWidth = MinWidth; } private void InvokeAction(Action action) { if (IsLoaded) { dispatcherTimer = new System.Windows.Threading.DispatcherTimer(); dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 3); // Reset speed speed = DEFAULT_SPEED; if (action == Action.Open) { // Open animation OpenAnimation(); } else { // Close animation CloseAnimation(); } // Start timer dispatcherTimer.Start(); } else if (action == Action.Close) { Visibility = Visibility.Collapsed; Width = exspectedMinWidth; } else if (action == Action.Open) { Visibility = Visibility.Visible; Width = exspectedMaxWidth; } } private void CloseAnimation() { dispatcherTimer.Tick += (sender, e) => { if (Width > exspectedMinWidth) { speed += 1; if (Width - exspectedMinWidth < speed) { speed = Width - exspectedMinWidth; } Width -= speed; } else { // Stop timer dispatcherTimer.Stop(); Visibility = Visibility.Collapsed; } }; } private void OpenAnimation() { Visibility = Visibility.Visible; dispatcherTimer.Tick += (sender, e) => { if (Width < exspectedMaxWidth) { speed += 1; if (exspectedMaxWidth - Width < speed) { speed = exspectedMaxWidth - Width; } Width += speed; } else { // Stop timer dispatcherTimer.Stop(); } }; } } }
Vậy là đã xong rồi đấy. Tiếp theo thử sử dụng nó xem nào!
MainWindow.xaml
<local:CustomGridPanel Background="#FFE91E63" IsOpen="{Binding IsChecked, ElementName=chk, UpdateSourceTrigger=PropertyChanged}" Width="200" Grid.Column="0"/>
Trong MainWindow tôi có 2 CheckBox và hai CustomGrid, giá trị thuộc tính IsOpen lần lược được Binding với thuộc tính IsChecked của CheckBox.
<Window x:Class="CustomControl.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:CustomControl" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525" Background="#FF009688" WindowStartupLocation="CenterScreen"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="40"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Border> <StackPanel Margin="0" Orientation="Horizontal" HorizontalAlignment="Center"> <CheckBox x:Name="chk" Content="IsOpen Right" IsChecked="True" Foreground="White" Background="White" BorderBrush="#FF004D40" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="3,0"/> <CheckBox x:Name="chk2" Content="IsOpen Left" IsChecked="True" Foreground="White" Background="White" BorderBrush="#FF004D40" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="3,0"/> </StackPanel> </Border> <Grid Grid.Row="1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Grid Background="#FF3F51B5" Grid.Column="0" Grid.ColumnSpan="3"/> <local:CustomGridPanel Background="#FFE91E63" IsOpen="{Binding IsChecked, ElementName=chk, UpdateSourceTrigger=PropertyChanged}" Width="200" Grid.Column="0"/> <local:CustomGridPanel Background="#FFE91E63" IsOpen="{Binding IsChecked, ElementName=chk2, UpdateSourceTrigger=PropertyChanged}" Width="200" Grid.Column="2"/> </Grid> </Grid> </Window>
Và kết quả là.
Bênh trong control này các bạn có thể bỏ vào đó bất kỳ thứ gì có thể là menu như đã nói ở đầu.
Nếu không muốn dùng cho Grid bạn có thể kế thừa từ Panel khác như Border hoặc chính class Panel cũng đc, bạn quan tâm có thể tải source tại đây, chúc các bạn thành công!
Very interesting info !Perfect just what I was searching for! “He who spares the wicked injures the good.” by Seneca.