کنترل adorner popup در تکنولوژی WPF

شنبه 1 آبان 1395

در این مقاله می خواهیم نحوه ی ایجاد یک کنترل را بیاموزیم که ظاهر و کارایی آن مشابه popup است و در لایه ی adorner قرار می گیرد. این کنترل می تواند زیباسازی نیز شود.

کنترل adorner popup در تکنولوژی WPF

 مقدمه

در همه تکنولوژی هایی که تا امروز ایجاد و توسعه داده شده اند، سعی شده است که با کمترین فضای ممکن ، بیشترین اطلاعات به کاربر نشان داده شود. برای این کار از مواردی نظیر dialog box و  popup boxاستفاده می شود. یکی دیگر از متدهایی که برای این کار استفاده می شود، کلاس Adorner است. این کلاس از این جهت با کلاس های دیگر متفاوت است که در لایه ی Adorner نمایش داده می شود که در بالای لایه ی UIElement در برنامه شما قرار دارد. ما در این مقاله می خواهیم از این کلاس استفاده کنیم و آن را در یک برنامه پیاده سازی کنیم.

PopupAdorner

کلاس PopupAdorner به عنوان یک نگهدارنده برای اطلاعات اضافی به کار می رود که بعدا می خواهیم آن ها را نمایش بدهیم. برای این که برنامه ، زیاد طولانی نشود ما کدی برای تنظیم مکان پنجره ی adorner ننوشته ایم.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

namespace UI
{
    public class PopupAdorner : Adorner
    {
        private VisualCollection _visuals;
        private ContentPresenter _presenter;
        
        /// <summary>
        /// Creates a new popup adorner with the specified content.
        /// </summary>
        /// <param name="adornedElement">The UIElement that will be adorned</param>
        /// <param name="content">The content that will be display inside the popup</param>
        /// <param name="offset">The popup position in regards to the adorned element</param>
        public PopupAdorner(UIElement adornedElement, UIElement content, Vector offset)
            : base(adornedElement)
        {
            _visuals = new VisualCollection(this);
            _presenter = new ContentPresenter();
            _visuals.Add(_presenter);
            _presenter.Content = content;
            Margin = new Thickness(offset.X, offset.Y, 0, 0);
        }
        
        protected override Size MeasureOverride(Size constraint)
        {
            _presenter.Measure(constraint);
            return _presenter.DesiredSize;
        }
        protected override Size ArrangeOverride(Size finalSize)
        {
            _presenter.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
            return _presenter.RenderSize;
        }
        protected override Visual GetVisualChild(int index)
        {
            return _visuals[index];
        }
        protected override int VisualChildrenCount
        {
            get
            {
                return _visuals.Count;
            }
        }
        
        /// <summary>
        /// Brings the popup into view.
        /// </summary>
        public void Show()
        {
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(AdornedElement);
            adornerLayer.Add(this);
        }
        /// <summary>
        /// Removes the popup into view.
        /// </summary>
        public void Hide()
        {
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(AdornedElement);
            adornerLayer.Remove(this);
        }
    }
}

با کلاس VisualCollection ما می توانیم نحوه نمایش کلاس ها و سایر موارد را تعیین کنیم. تنها محدودیتی که در رابطه با این کلاس وجود دارد، این است که فقط می توانیم یک شی پدر (parent object) ایجاد کنیم.

تعریف محتویات Adorner

دو راه برای ساخت محتویات وجود دارد :ساخت یک UserControl سفارشی و یا استفاده از code behind . ما در این مقاله از روش کدنویسی استفاده می کنیم.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace UI
{
    public static class DynamicAdornerContent
    {
        public static Border GetSimpleInfoPopup(string[] textInfo)
        {
            Border popupBorder = new Border() 
                { BorderBrush = Brushes.Black, BorderThickness = new Thickness(1D) };
            Grid container = new Grid() 
                { Background = Brushes.White };
            
            Style txtStyle = new Style();
            txtStyle.TargetType = typeof(TextBlock);
            txtStyle.Setters.Add(new Setter() 
                { 
                    Property = FrameworkElement.HorizontalAlignmentProperty, 
                    Value = HorizontalAlignment.Left 
                });
            txtStyle.Setters.Add(new Setter() 
                { 
                    Property = FrameworkElement.VerticalAlignmentProperty, 
                    Value = VerticalAlignment.Center 
                });
            
            for (int i = 0; i < textInfo.Length; ++i)
            {
                container.RowDefintions.Add(new RowDefinition() 
                    { Height = new GridLength(26D) };

                var tbox = new TextBlock();
                tbox.Style = txtStyle;
                tbox.Text = textInfo[i];
                
                Grid.SetRow(tbox, i);
                container.Children.Add(tbox);
            }
            
            popupBorder.Child = container;
            return popupBorder;
        }
    }
}

استفاده از PopupAdorner

Adorner می تواند برای هر نوعی از UIElement ها استفاده شود. برای این کار ما یک user control سفارشی می سازیم که شامل یک TextBlock است و یک تصویر کوچک نیز دارد که زمانی که بر روی آن کلیک شود ، PopupAdorner گسترده می شود.

<UserControl>
    <UserControl.Resources>
        <DrawingImage x:Key="img_Arrow">
            <DrawingImage.Drawing>
                <DrawingGroup>
                    <GeometryDrawing Brush="Gray">
                        <GeometryDrawing.Pen>
                            <Pen Brush="DarkGray" 
                                EndLineCap="Round" 
                                LineJoin="Round" 
                                StartLineCap="Round" 
                                Thickness="1"/>
                        </GeometryDrawing.Pen>
                        <GeometryDrawing.Geometry>
                            <PathGeometry>
                                <PathFigure IsFilled="True" StartPoint="0,5">
                                    <PathFigure.Segments>
                                        <LineSegment Point="5,0"/>
                                        <LineSegment Point="5,10"/>
                                        <LineSegment Point="0,5"/>
                                    </Pathfigure.Segments>
                                </PathFigure>
                            </PathGeometry>
                        </GeometryDrawing.Geometry>
                    </GeometryDrawing>
                </DrawingGroup>
            </DrawingImage.Drawing>
        </DrawingImage>
    </UserControl.Resources>
    <Grid Background="White" Height="28" Width="300" x:Name="grid_Container">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="20"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" 
            HorizontalAlignment="Stretch" 
            TextTrimming="CharacterEllipsis" 
            TextWrapping="NoWrap" 
            VerticalAlignment="Center" 
            x:Name="txt_DisplayText"/>
        <Image Grid.Column="1" 
            Height="18" 
            MouseDown="TogglePopup" 
            Source="{StaticResource ResourceKey=img_Arrow}" 
            ToolTip="Expand" 
            Width="16" 
            x:Name="ctl_Expander"/>
    </Grid>
</UserControl>

Code Behind

using System;
using System.Windows;
using System.Windows.Controls;
using System.Widnows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace UI
{
    public class ControlWithPopup : UserControl
    {
        private Border _infoContainer;
        private PopupAdorner _infoPopup;
        private bool _isExpanded;
        
        public ControlWithPopup(string displayText, string[] additionalInfo)
        {
            InitializeComponent();
            _displayText.Text = displayText;
            _infoContainer = DynamicAdornerContent.GetSimpleInfoPopup(additionalInfo);
        }
        
        private TogglePopup(sender object, MouseButtonEventArgs e)
        {
            if (_isExpanded)
            {
                _infoPopup.Hide();
                _isExpanded = false;
            }
            else
            {
                if (_infoPopup == null)
                {
                    _infoPopup = new PopupAdorner(
                        grid_Container, 
                        _infoContainer, 
                        new Vector(0, 29));
                }
                
                _infoPopup.Show();
                _isExpanded = true;
            }
        }
    }
}

با استفاده از این کد، زمانی که کاربر بر روی تصویر دکمه جهت دار، کلیک می کند، اطلاعات برای او نمایش داده می شود. سپس زمانی که دوباره بر روی کلید جهتی کلیک کند، اطلاعات ناپدید می شوند.

اضافه کردن Animation

کاری که در این قسمت نیاز داریم انجام بدهیم اضافه کردن کمی ویژگی های بصری به برنامه است.

// add these animations, transforms, and method to the ControlWithPopup class
private DoubleAnimation _extendAnimation;
private DoubleAnimation _collapseAnimation;
private DoubleAnimation _extendArrowAnimation;
private DoubleAnimation _collapseArrowAnimation;
private RotateTransform _extendArrowTransform;
private RotateTransform _collapseArrowTransform;

// call this in the constructor to build the animations for rotating the arrow in response to user interactions
private void InitializeAnimations()
{
    _extendArrowAnimation = 
        new DoubleAnimation(0.0, -90.0, new Duration(TimeSpan.FromSeconds(0.25)));
    _collapseArrowAnimation = 
        new DoubleAnimation(-90.0, 0.0, new Duration(TimeSpan.FromSeconds(0.25)));
    _extendArrowTransform = 
        new RotateTransform() { Angle = -90, CenterX = 0.5, CenterY = 0.5 };
    _collapseArrowTransform = 
        new RotateTransform() { Angle = 0, CenterX = 0.5, CenterY = 0.5 };
}

// alter the TogglePopup function to include animation calls
private TogglePopup(sender object, MouseButtonEventArgs e)
{
    ctl_Expander.RenderTransformOrigin = new Point(0.5, 0.5);
    
    if (_isExpanded)
    {
        ctl_Expander.RenderTransform = _collapseArrowTransform;
        ctl_Expander.BeginAnimation(RotateTransform.AngleProperty, _collapseArrowAnimation);
        
        _collapseAnimation = new DoubleAnimation(
            _infoContainer.ActualHeight, 
            0.0, 
            new Duration(TimeSpan.FromSeconds(0.25)), 
            FillBehavior.Stop);
        // this next line will prevent the popup from being removed from the
        // AdornerLayer until the animation completes
        _collapseAnimation.Completed += (s, ev) => { _infoPopup.Hide(); }; 
        
        _infoPopup.BeginAnimation(HeightProperty, _collapseAnimation);
        _isExpanded = false;
    }
    else
    {
         if (_infoPopup == null)
        {
             _infoPopup = new PopupAdorner(grid_Container, _infoContainer, new Vector(0, 29));
             _infoContainer.Measure(new Size(Double.PositiveInfinity, Double.PositiveInifity));
             
             _extendAnimation = new DoubleAnimation(
                0.0, 
                _infoContainer.DesiredSize.Height, 
                new Duration(TimeSpan.FromSeconds(0.25)), 
                FillBehavior.Stop);
             
            // this line will cause the extend animation to begin after the container
            // information is loaded
             _infoContainer.Loaded += (s, ev) => 
                { 
                    _infoContainer.BeginAnimation(HeightProperty, _extendAnimation; 
                }
        }
        
        ctl_Expander.RenderTransform = extendArrowTransform;
        ctl_Expander.BeginAnimation(RotateTransform.AngleProperty, _extendArrowAnimation);
        
        _infoPopup.Show();
        _isExpanded = true;
    }
}

مواردی که اضافه کردیم شامل چهارشی DoubleAnimation و دو شی RotateTransform است. این موارد در زمان پاسخگویی برنامه به کاربر فعال می شوند. زمانی که بر روی دکمه جهتی کلیک شود، adorner popup به سمت پایین حرکت کرده و به اصطلاح باز می شود. و زمانی که کاربر دوباره بر روی کلید جهتی کلیک کند، adorner popup به سمت بالا حرکت کرده و جمع می شود. در هر بار اجرای این روند، اگر لازم باشد adorner، محتویات را به AdornerLayer می برد و در صورت لزوم آن ها را برمی گرداند.

فایل های ضمیمه

سجاد باقرزاده

نویسنده 54 مقاله در برنامه نویسان
  • WPF
  • 2k بازدید
  • 0 تشکر

کاربرانی که از نویسنده این مقاله تشکر کرده اند

تاکنون هیچ کاربری از این پست تشکر نکرده است

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید