WPF – Biến Grid Thành Slide Navigation Menu

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.

Slide navigation bar wpf

Slider Navigation bar

Ý 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à.

Navigation bar wpf

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!

Phạm Tuân

Tạo bộ cài đặt offline và key bản quyền cho Visual St...
Tạo ChatBot đơn giản hơn với Dialogflow – Phần 2
Kích Koạt Office 2021 và Office 365 Bản Quyền Miễn Phi...
Hãy bình luận trực tiếp ở đây để được trả lời nhanh hơn. 1 bình luận.

Trả lời