آموزش ساخت TextBox سفارشی در WPF
در این بخش آموزش ساخت TextBox سفارشی در WPF را برای شما آماده کرده ایم که یک آموزش مناسب برای ساخت کنترل های سفارشی با استفاده از زبان برنامه نویسی سی شارپ و تکنولوژی Xaml است. در ادامه می توانید توضیحات، تصاویر و همچنین فیلمی از نتیجه نهایی را مشاهده کنید.
توضیحات
در این آموزش قصد داریم تا استایل استفاده شده برای فیلدهای مروگر کروم را در WPF ایجاد کنیم. برای اینکه متوجه شوید منظورمان کدام فیلد است به تصویر زیر نگاه کنید.
ایجاد کنترل TextField
ابتدا با توجه به تصویر زیر یک پروژه از نوع WPF Custom Control Library ایجاد کنید.
بعد از ایجاد شدن پروژه هم می توانیم کنترلی که به طور پیش فرض ایجاد می شود را تغییر نام دهیم و هم می توانیم آن را حذف کرده و کنترل جدید ایجاد کنیم. ما همان کنترلی که به صورت پیشفرض ایجاد می شود (CustomControl1) را تغییر نام می دهیم به TextField و کد زیر را در داخل آن مینویسیم.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | using System.Windows; using System.Windows.Controls; namespace CustomTextBox { public class TextField : TextBox { #region Constructors static TextField() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TextField), new FrameworkPropertyMetadata(typeof(TextField))); } #endregion #region DependencyProperty : Caption public static readonly DependencyProperty CaptionProperty = DependencyProperty.Register(nameof(Caption), typeof(object), typeof(TextField), new PropertyMetadata(default)); public object Caption { get => GetValue(CaptionProperty); set => SetValue(CaptionProperty, value); } #endregion #region DependencyProperty : CaptionFontSize public static readonly DependencyProperty CaptionFontSizeProperty = DependencyProperty.Register(nameof(CaptionFontSize), typeof(double), typeof(TextField), new PropertyMetadata(default)); public double CaptionFontSize { get => (double)GetValue(CaptionFontSizeProperty); set => SetValue(CaptionFontSizeProperty, value); } #endregion #region DependencyProperty : CaptionOpacity public static readonly DependencyProperty CaptionOpacityProperty = DependencyProperty.Register(nameof(CaptionOpacity), typeof(double), typeof(TextField), new PropertyMetadata(default)); public double CaptionOpacity { get => (double)GetValue(CaptionOpacityProperty); set => SetValue(CaptionOpacityProperty, value); } #endregion #region DependencyProperty : CaptionAlignment public static readonly DependencyProperty CaptionAlignmentProperty = DependencyProperty.Register(nameof(CaptionAlignment), typeof(HorizontalAlignment), typeof(TextField), new PropertyMetadata(default)); public HorizontalAlignment CaptionAlignment { get => (HorizontalAlignment)GetValue(CaptionAlignmentProperty); set => SetValue(CaptionAlignmentProperty, value); } #endregion #region DependencyProperty : CornerRadius public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(TextField), new PropertyMetadata(default)); public CornerRadius CornerRadius { get => (CornerRadius)GetValue(CornerRadiusProperty); set => SetValue(CornerRadiusProperty, value); } #endregion } } |
از آنجایی که قرار است ما یک TextBox ایجاد کنیم، پس کنترل ما باید از TextBox ارث بری کند. کد زیر که در داخل سازنده استاتیک نوشته شده است، برای override کردن متادیتا مربوط به کنترل والد است. برای مثال در این بخش می توانیم متادیتاهایی مثل مقدار پیشفرض، نوع بایند شدن و غیره را تنظیم کنیم.
1 2 3 4 5 | static TextField() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TextField), new FrameworkPropertyMetadata(typeof(TextField))); } |
به جز سازنده فوق، کد مربوط به کنترل ما شامل 5 خاصیت دیگر نیز است که همه از نوع DependencyProperty هستند و در ادامه هر کدام را توضیح خواهم داد.
- خاصیت Caption: این خاصیت مقداری که در بالای فیلد نمایش داده می شود را مشخص می کند.
- خاصیت CaptionFontSize: همانطور که از اسمش نیز مشخص است، اندازه فونت مربوط به Caption را مشخص می کند.
- خاصیت CaptionOpacity: میزان شفافیت Caption را مشخص می کند.
- خاصیت CaptionAlignment: محل قرار گیری Caption را مشخص می کند و می تواند با مقادیر Stretch، Left، Right و Center تنظیم شود.
- خاصیت CornerRadius: میزان گردی گوشه های فیلد را مشخص می کند.
حال باید ظاهر کنترل را درست کنیم. به صورت پیشفرض در داخل فایل Generic.xaml یک استایل برای کنترل جدید ایجاد می شود که ما آن را به شکل زیر تغییر می دهیم.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CustomTextBox" xmlns:converters="clr-namespace:CustomTextBox.Converters"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary> <converters:TextFieldHeightConverter x:Key="TextFieldHeightConverter"/> </ResourceDictionary> </ResourceDictionary.MergedDictionaries> <Style TargetType="{x:Type local:TextField}"> <Setter Property="UseLayoutRounding" Value="True"/> <Setter Property="AllowDrop" Value="True"/> <Setter Property="KeyboardNavigation.TabNavigation" Value="None"/> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Setter Property="VerticalScrollBarVisibility" Value="Auto"/> <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/> <Setter Property="MinWidth" Value="120"/> <Setter Property="MinHeight" Value="34"/> <Setter Property="Height" Value="38"/> <Setter Property="Background" Value="#eeeeee"/> <Setter Property="Foreground" Value="#121212"/> <Setter Property="BorderBrush" Value="#3399ff"/> <Setter Property="CornerRadius" Value="4"/> <Setter Property="Cursor" Value="IBeam"/> <Setter Property="FontSize" Value="13"/> <Setter Property="Padding" Value="4"/> <Setter Property="Margin" Value="4"/> <Setter Property="CaptionFontSize" Value="10"/> <Setter Property="CaptionOpacity" Value="0.64"/> <Setter Property="CaptionAlignment" Value="Left"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:TextField}"> <Grid Name="PART_ContainerGrid"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="FocusStates"> <VisualState x:Name="Focused"> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="ScaleX" Storyboard.TargetName="UnderlineScaleTransform" From="0" To="1" Duration="0:0:0.20"> <DoubleAnimation.EasingFunction> <SineEase EasingMode="EaseOut"/> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </VisualState> <VisualState x:Name="Unfocused"> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="ScaleX" Storyboard.TargetName="UnderlineScaleTransform" To="0" Duration="0"/> <DoubleAnimation Storyboard.TargetProperty="ScaleY" Storyboard.TargetName="UnderlineScaleTransform" To="0" Duration="0"/> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <ContentPresenter Name="PART_CaptionHost" Grid.Row="0" Margin="0,0,0,6" Cursor="Arrow" HorizontalAlignment="{TemplateBinding CaptionAlignment}" Opacity="{TemplateBinding CaptionOpacity}" TextElement.FontSize="{TemplateBinding CaptionFontSize}" TextElement.FontFamily="{TemplateBinding FontFamily}" TextElement.FontWeight="{TemplateBinding FontWeight}" TextElement.Foreground="{TemplateBinding Foreground}" Content="{TemplateBinding Caption}"/> <Border Grid.Row="1" CornerRadius="{TemplateBinding CornerRadius}" Background="{TemplateBinding Background}" BorderBrush="Transparent" BorderThickness="0"> <Border.Height> <MultiBinding Converter="{StaticResource TextFieldHeightConverter}"> <Binding RelativeSource="{RelativeSource AncestorType={x:Type local:TextField}}"/> <Binding ElementName="PART_ContainerGrid"/> </MultiBinding> </Border.Height> <Grid> <Border Name="PART_UnderlineBorder" VerticalAlignment="Bottom" Height="{Binding RelativeSource={RelativeSource AncestorType=Border}, Path=ActualHeight}" Width="{TemplateBinding ActualWidth}" CornerRadius="{TemplateBinding CornerRadius}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0,0,0,4" RenderTransformOrigin="0.5,0.5"> <Border.RenderTransform> <ScaleTransform x:Name="UnderlineScaleTransform" ScaleX="0" ScaleY="1"/> </Border.RenderTransform> <Border.LayoutTransform> <ScaleTransform ScaleX="1" ScaleY="0.54"/> </Border.LayoutTransform> </Border> <ScrollViewer Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/> </Grid> </Border> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Opacity" Value="0.64" TargetName="PART_ContentHost"/> </Trigger> <Trigger Property="IsKeyboardFocused" Value="True"> <Setter Property="TextElement.Foreground" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextField}}, Path=BorderBrush }" TargetName="PART_CaptionHost"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary> |
در ادامه بخش های مهم کد فوق را بررسی می کنیم.
1 2 3 4 5 | <ResourceDictionary.MergedDictionaries> <ResourceDictionary> <converters:TextFieldHeightConverter x:Key="TextFieldHeightConverter"/> </ResourceDictionary> </ResourceDictionary.MergedDictionaries> |
کد بالا Converter زیر را اضافه می کند که کار آن تنظیم کردن ارتفاع کنترل با ارتفاع PART_ContainerGrid و PART_CaptionHost است. در زیر می توانید کد مربوط به این Converter را مشاهده کنید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | using System; using System.Globalization; using System.Windows.Controls; using System.Windows.Data; namespace CustomTextBox.Converters { internal class TextFieldHeightConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values.Length != 2) return 0; var textField = values[0] as TextField; var containerGrid = values[1] as Grid; var result = containerGrid.ActualHeight; textField.Height = double.NaN; containerGrid.Height = double.NaN; return result; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } } |
کد زیر مقادیر پیش فرض را برای کنترل TextField تنظیم می کند.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <Setter Property="UseLayoutRounding" Value="True"/> <Setter Property="AllowDrop" Value="True"/> <Setter Property="KeyboardNavigation.TabNavigation" Value="None"/> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Setter Property="VerticalScrollBarVisibility" Value="Auto"/> <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/> <Setter Property="MinWidth" Value="120"/> <Setter Property="MinHeight" Value="34"/> <Setter Property="Height" Value="38"/> <Setter Property="Background" Value=" #eeeeee"/> <Setter Property="Foreground" Value="#121212"/> <Setter Property="BorderBrush" Value="#3399ff"/> <Setter Property="CornerRadius" Value="4"/> <Setter Property="Cursor" Value="IBeam"/> <Setter Property="FontSize" Value="13"/> <Setter Property="Padding" Value="4"/> <Setter Property="Margin" Value="4"/> <Setter Property="CaptionFontSize" Value="10"/> <Setter Property="CaptionOpacity" Value="0.64"/> <Setter Property="CaptionAlignment" Value="Left"/> |
کد زیر انیمیشنی که باید در حالت Focus و Unfocus بر روی PART_Underline اعمال شوند را تنظیم می کند.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="FocusStates"> <VisualState x:Name="Focused"> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="ScaleX" Storyboard.TargetName="UnderlineScaleTransform" From="0" To="1" Duration="0:0:0.20"> <DoubleAnimation.EasingFunction> <SineEase EasingMode="EaseOut"/> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </VisualState> <VisualState x:Name="Unfocused"> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="ScaleX" Storyboard.TargetName="UnderlineScaleTransform" To="0" Duration="0"/> <DoubleAnimation Storyboard.TargetProperty="ScaleY" Storyboard.TargetName="UnderlineScaleTransform" To="0" Duration="0"/> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> |
کد زیر مقدار تنظیم شده برای خاصیت Caption را در بالای فیلد نمایش می دهد.
1 2 3 4 5 6 7 8 9 10 11 12 | <ContentPresenter Name="PART_CaptionHost" Grid.Row="0" Margin="0,0,0,6" Cursor="Arrow" HorizontalAlignment="{TemplateBinding CaptionAlignment}" Opacity="{TemplateBinding CaptionOpacity}" TextElement.FontSize="{TemplateBinding CaptionFontSize}" TextElement.FontFamily="{TemplateBinding FontFamily}" TextElement.FontWeight="{TemplateBinding FontWeight}" TextElement.Foreground="{TemplateBinding Foreground}" Content="{TemplateBinding Caption}"/> |
کد زیر مربوط به خطی است که در هنگام Focus زیر فیلد نمایش داده می شود.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <Border Name="PART_UnderlineBorder" VerticalAlignment="Bottom" Height="{Binding RelativeSource={RelativeSource AncestorType=Border}, Path=ActualHeight}" Width="{TemplateBinding ActualWidth}" CornerRadius="{TemplateBinding CornerRadius}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0,0,0,4" RenderTransformOrigin="0.5,0.5"> <Border.RenderTransform> <ScaleTransform x:Name="UnderlineScaleTransform" ScaleX="0" ScaleY="1"/> </Border.RenderTransform> <Border.LayoutTransform> <ScaleTransform ScaleX="1" ScaleY="0.54"/> </Border.LayoutTransform> </Border> |
کد زیر مقدار تنظیم شده برای خاصیت Text را نمایش می دهد.
1 2 3 | <ScrollViewer Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/> |
Trigger زیر در هنگام Focus شدن به داخل فیلد، رنگ متن Caption را با رنگ Border تنظیم می کند.
1 2 3 4 5 6 | <Trigger Property="IsKeyboardFocused" Value="True"> <Setter Property="TextElement.Foreground" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextField}}, Path=BorderBrush }" TargetName="PART_CaptionHost"/> </Trigger> |
استفاده از کنترل TextField
برای استفاده از کنترلی که تا اینجای آموزش ایجاد کرده ایم، یک پروژه از نوع WPF ایجاد کنید و سپس Reference مربوط به پروژه بالا را به این پروژه اضافه کنید. بعد از افزودن Reference کنترل، باید Resource مربوط به ظاهر کنترل را هم اضافه کنیم. برای این کار محتوای فایل App.xaml را به شکل زیر تغییر دهید.
1 2 3 4 5 6 7 8 9 10 11 12 13 | <Application x:Class="WpfSandbox.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="pack://application:,,,/CustomTextBox;component/Themes/Generic.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application> |
حال محتوای مربوط به فایل MainWindow.xaml را نیز به شکل زیر تغییر دهید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <Window x:Class="WpfSandbox.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:cmp="clr-namespace:CustomTextBox;assembly=CustomTextBox" mc:Ignorable="d" Title="SourceSara.Com" Height="320" Width="500" Background="#28292C"> <Window.Resources> <Style TargetType="{x:Type cmp:TextField}"> <Setter Property="FontFamily" Value="Roboto"/> <Setter Property="Margin" Value="8"/> <Setter Property="MinWidth" Value="220"/> <Setter Property="Height" Value="34"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Foreground" Value="#e8eaed"/> <Setter Property="Background" Value="#1D1D1F"/> <Setter Property="BorderBrush" Value="#8ab4f8"/> <Setter Property="CaretBrush" Value="#8ab4f8"/> </Style> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" FontSize="24" Margin="0,16" TextAlignment="Center" Foreground="#8ab4f8" Text="Google Chrome Text Field Style"/> <StackPanel Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"> <cmp:TextField Margin="8" Caption="Name on card" Text="Sample card name"/> <cmp:TextField Margin="8" Caption="Card number" Text="6037997411112222"/> </StackPanel> </Grid> </Window> |
مشاهده نتیجه
اگر پروژه WPF را Build و Run کنید، خروجی زیر را مشاهده خواهید کرد.
هیچ نظری ثبت نشده است