آموزش ساخت RadioButton سفارشی در WPF
در این بخش آموزش ساخت RadioButton سفارشی در WPF را برای شما آماده کرده ایم که یک آموزش مناسب برای ساخت کنترل های سفارشی با استفاده از زبان برنامه نویسی سی شارپ و تکنولوژی Xaml است. در ادامه می توانید توضیحات، تصاویر و همچنین فیلمی از نتیجه نهایی را مشاهده کنید.
توضیحات
برای ساخت این پروژه از نرم افزار Visual Studio نسخه 2019 و فریم ورک .Net Core 3.1 استفاده شده است. البته شما می توانید با اعمال تغییرات کوچکی پروژه را در نسخه های پایین تر نیز اجرا کنید. نتیجه نهایی کار به شکل زیر خواهد بود.
مراحل آموزش
- ایجاد پروژه WPF Custom Control Library
- ایجاد کنترل CustomRadioButton
- ایجاد پروژه WPF
- استفاده از کنترل CustomRadioButton
ایجاد پروژه WPF Custom Control Library
ابتدا با توجه به تصویر زیر یک پروژه از نوع WPF Custom Control Library ایجاد کنید. برای بهتر شدن ساختار پروژه بهتر است ابتدا یک Blank Solution ایجاد کنید و سپس پروژه های مورد نظرتان را به آن اضافه کنید.
ایجاد کنترل CustomRadioButton
به صورت پیشفرض بعد از ایجاد شدن پروژه، یک کنترل با نام CustomControl1 ایجاد می شود. به جای ایجاد کردن کنترل جدید ما همان کنترل را تغییر نام می دهیم به CustomRadioButton و کدهای زیر را در داخل آن مینویسیم.
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 | using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace CustomRadioButton { public class CustomRadioButton : RadioButton { #region Constructors static CustomRadioButton() { DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomRadioButton), new FrameworkPropertyMetadata(typeof(CustomRadioButton))); } #endregion #region DependencyProperty : CheckedBrush public static readonly DependencyProperty CheckedBrushProperty = DependencyProperty.Register(nameof(CheckedBrush), typeof(SolidColorBrush), typeof(CustomRadioButton), new PropertyMetadata(default)); public SolidColorBrush CheckedBrush { get => (SolidColorBrush)GetValue(CheckedBrushProperty); set => SetValue(CheckedBrushProperty, value); } #endregion #region DependencyProperty : BulletCircleBrush public static readonly DependencyProperty BulletCircleBrushProperty = DependencyProperty.Register(nameof(BulletCircleBrush), typeof(SolidColorBrush), typeof(CustomRadioButton), new PropertyMetadata(default)); public SolidColorBrush BulletCircleBrush { get => (SolidColorBrush)GetValue(BulletCircleBrushProperty); set => SetValue(BulletCircleBrushProperty, value); } #endregion #region DependencyProperty : BulletSize public static readonly DependencyProperty BulletSizeProperty = DependencyProperty.Register(nameof(BulletSize), typeof(double), typeof(CustomRadioButton), new PropertyMetadata(default)); public double BulletSize { get => (double)GetValue(BulletSizeProperty); set => SetValue(BulletSizeProperty, value); } #endregion #region DependencyProperty : UncheckedBulletBorderSize public static readonly DependencyProperty UncheckedBulletBorderSizeProperty = DependencyProperty.Register(nameof(UncheckedBulletBorderSize), typeof(double), typeof(CustomRadioButton), new PropertyMetadata(default)); public double UncheckedBulletBorderSize { get => (double)GetValue(UncheckedBulletBorderSizeProperty); set => SetValue(UncheckedBulletBorderSizeProperty, value); } #endregion #region DependencyProperty : CornerRadius public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(CustomRadioButton), new PropertyMetadata(default)); public CornerRadius CornerRadius { get => (CornerRadius)GetValue(CornerRadiusProperty); set => SetValue(CornerRadiusProperty, value); } #endregion } } |
همانطور که مشاهده می کنید، کدهای C# مربوط به کنترل سفارشی ما چندان پیچیده نیست و فقط شامل یک سازنده استاتیک و 5 خاصیت از نوع DependencyProperty است.
کد زیر که در داخل سازنده استاتیک نوشته شده است، برای override کردن متادیتا مربوط به کنترل والد است.
1 2 3 4 5 | static CustomRadioButton() { DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomRadioButton), new FrameworkPropertyMetadata(typeof(CustomRadioButton))); } |
بقیه کد بالا مربوط به خاصیت ها است که در ادامه هر کدام را توضیح می دهم.
- خاصیت CheckedBrush: این خاصیت رنگ حالتی که RadioButton به صورت انتخاب شده است را مشخص می کند.
- خاصیت BulletCircleBrush: این خاصیت رنگ دایره کوچکی که در داخل RadioButton انتخاب شده قرار دارد را مشخص می کند.
- خاصیت BulletSize: این خاصیت اندازه گوله ای مربوط به RadioButton را مشخص می کند.
- خاصیت UncheckedBulletBorderSize: این خاصیت اندازه حاشیه گوله RadioButton را در حالت انتخاب نشده، مشخص می کند.
- خاصیت CornerRadius: میزان گردی گوشه های کنترل را مشخص می کند.
در زیر می توانید کد Converter مربوط به رنگ پس زمینه در حالت انتخاب شده را مشاهده کنید. این تبدیل کننده رنگ CheckedBrush را می گیرد و یک Brush جدید با میزان شفافیت مشخص شده ایجاد می کند.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class BrushOpacityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var opacity = System.Convert.ToDouble(parameter); var checkBrush = value as SolidColorBrush; return new SolidColorBrush(checkBrush.Color) { Opacity = opacity }; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } |
بعد از انجام مراحل بالا حال باید ظاهر کنترل را درست کنیم. به صورت پیشفرض در داخل فایل 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 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CustomRadioButton" xmlns:converters="clr-namespace:CustomRadioButton.Converters"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary> <converters:BrushOpacityConverter x:Key="BrushOpacityConverter"/> </ResourceDictionary> </ResourceDictionary.MergedDictionaries> <Style x:Key="FocusVisual"> <Setter Property="Control.Template"> <Setter.Value> <ControlTemplate> <Rectangle Margin="0,0,0,0" UseLayoutRounding="True" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="{x:Type local:CustomRadioButton}"> <Setter Property="UseLayoutRounding" Value="True"/> <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> <Setter Property="Margin" Value="6"/> <Setter Property="Padding" Value="12,10"/> <Setter Property="Cursor" Value="Hand"/> <Setter Property="CornerRadius" Value="4"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="CheckedBrush" Value="#3399ff"/> <Setter Property="BulletCircleBrush" Value="#ffffff"/> <Setter Property="BulletSize" Value="24"/> <Setter Property="UncheckedBulletBorderSize" Value="1"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:CustomRadioButton}"> <Grid> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="FocusStates"> <VisualState x:Name="Checked"> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="ScaleX" Storyboard.TargetName="CheckedEllipseContainerScaleTransform" To="1" Duration="0:0:0.12"/> <DoubleAnimation Storyboard.TargetProperty="ScaleY" Storyboard.TargetName="CheckedEllipseContainerScaleTransform" To="1" Duration="0:0:0.12"/> </Storyboard> </VisualState> <VisualState x:Name="Unchecked"> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="ScaleX" Storyboard.TargetName="CheckedEllipseContainerScaleTransform" To="0" Duration="0"/> <DoubleAnimation Storyboard.TargetProperty="ScaleY" Storyboard.TargetName="CheckedEllipseContainerScaleTransform" To="0" Duration="0"/> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Border Name="PART_BackgroundBorder" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="Transparent" CornerRadius="{TemplateBinding CornerRadius}" Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Grid}}, Path=ActualWidth}" Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Grid}}, Path=ActualHeight}"/> <BulletDecorator Name="PART_BulletDecorator" Background="Transparent" Margin="{TemplateBinding Padding}" UseLayoutRounding="{TemplateBinding UseLayoutRounding}"> <BulletDecorator.Bullet> <Grid Width="{TemplateBinding BulletSize}" Height="{TemplateBinding BulletSize}" VerticalAlignment="Center"> <Ellipse Name="PART_UncheckedEllipse" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="{TemplateBinding UncheckedBulletBorderSize}"/> <Grid Name="PART_CheckedEllipseContainerGrid" RenderTransformOrigin="0.5,0.5"> <Grid.RenderTransform> <ScaleTransform x:Name="CheckedEllipseContainerScaleTransform" ScaleX="0" ScaleY="0"/> </Grid.RenderTransform> <Ellipse Name="PART_CheckedEllipse" Fill="{TemplateBinding CheckedBrush}"> <Ellipse.Effect> <DropShadowEffect Color="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=CheckedBrush.Color}" Opacity="0.64" ShadowDepth="0" BlurRadius="24"/> </Ellipse.Effect> </Ellipse> <Ellipse Width="{Binding ElementName=PART_CheckedEllipse, Path=ActualWidth}" Height="{Binding ElementName=PART_CheckedEllipse, Path=ActualHeight}" Fill="{TemplateBinding BulletCircleBrush}" RenderTransformOrigin="0.5,0.5"> <Ellipse.RenderTransform> <ScaleTransform ScaleX="0.5" ScaleY="0.5"/> </Ellipse.RenderTransform> </Ellipse> </Grid> </Grid> </BulletDecorator.Bullet> <ContentPresenter Margin="6,0,0,0" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True"/> </BulletDecorator> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsChecked" Value="True"> <Setter TargetName="PART_BackgroundBorder" Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=CheckedBrush}"/> <Setter TargetName="PART_BackgroundBorder" Property="Background" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=CheckedBrush, Converter={StaticResource BrushOpacityConverter}, ConverterParameter=0.164}"/> </Trigger> <Trigger Property="IsEnabled" Value="False"> <Setter TargetName="PART_BulletDecorator" Property="Opacity" Value="0.56"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary> |
در ادامه بخش های مهم کد بالا را توضیح می دهم. کد زیر تبدیل کننده ای رنگ پس زمینه را به منابع اضافه می کند.
1 2 3 4 5 | <ResourceDictionary.MergedDictionaries> <ResourceDictionary> <converters:BrushOpacityConverter x:Key="BrushOpacityConverter"/> </ResourceDictionary> </ResourceDictionary.MergedDictionaries> |
کد زیر استایل حالتی است که بر روی کنترل فوکوس می شود.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <Style x:Key="FocusVisual"> <Setter Property="Control.Template"> <Setter.Value> <ControlTemplate> <Rectangle Margin="0,0,0,0" UseLayoutRounding="True" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/> </ControlTemplate> </Setter.Value> </Setter> </Style> |
کد زیر مربوط به استایل پیش فرض کنترل است.
1 2 3 4 5 6 7 8 9 10 11 12 13 | <Setter Property="UseLayoutRounding" Value="True"/> <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> <Setter Property="Margin" Value="6"/> <Setter Property="Padding" Value="12,10"/> <Setter Property="Cursor" Value="Hand"/> <Setter Property="CornerRadius" Value="4"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="CheckedBrush" Value="#3399ff"/> <Setter Property="BulletCircleBrush" Value="#ffffff"/> <Setter Property="BulletSize" Value="24"/> <Setter Property="UncheckedBulletBorderSize" Value="1"/> |
Visual State های مربوط به حالت انتخاب شده و انتخاب نشده. اگر کنترل در حالت انتخاب شده قرار گیرد، انیمیشن بخش Checked اجرا می شود و میزان مقیاس کنترل CheckedEllipseContainerScaleTransform را به 1 تغییر می دهد. و اگر در حالت انتخاب نشده قرار بگیرد، مقیاس به 0 تغییر می کند.
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 | <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="FocusStates"> <VisualState x:Name="Checked"> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="ScaleX" Storyboard.TargetName="CheckedEllipseContainerScaleTransform" To="1" Duration="0:0:0.12"/> <DoubleAnimation Storyboard.TargetProperty="ScaleY" Storyboard.TargetName="CheckedEllipseContainerScaleTransform" To="1" Duration="0:0:0.12"/> </Storyboard> </VisualState> <VisualState x:Name="Unchecked"> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="ScaleX" Storyboard.TargetName="CheckedEllipseContainerScaleTransform" To="0" Duration="0"/> <DoubleAnimation Storyboard.TargetProperty="ScaleY" Storyboard.TargetName="CheckedEllipseContainerScaleTransform" To="0" Duration="0"/> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> |
به جز Visual State های فوق و Setter زیر بخش چندان پیچیده ای در بخش Template وجود ندارد.
1 2 3 4 5 6 7 8 | <Setter TargetName="PART_BackgroundBorder" Property="Background" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=CheckedBrush, Converter={StaticResource BrushOpacityConverter}, ConverterParameter=0.164}"/> |
خوب این Setter زمانی اعمال می شود که کنترل در حالت انتخاب شده قرار بگیرد. برای رنگ پس زمینه ما از خاصیت CheckedBrush استفاده می کنیم. به این صورت که آن را به تبدیل کننده ای که ایجاد کرده بودیم ارسال می کنیم و میزان شفافیت را نیز به عنوان پارامتر تبدیل کننده ارسال می کنیم تا رنگ مورد نظرمان را بدست آوریم. در کد بالا خاصیت CheckedBrush با شفافیت 0.164 برای رنگ پس زمینه کنترل تنظیم می شود.
ایجاد پروژه WPF
تا اینجای کار ما کنترل مورد نظرمان را ایجاد کردیم، حال باید آن را تست کنیم. برای این کار یک پروژه از نوع WPF ایجاد کنید و پروژه بالا را به عنوان Reference به این پرژه اضافه کنید.
استفاده از کنترل CustomRadioButton
بعد از انجام مرحله بالا، فایل App.xaml را باز کنید و Resource مربوط به کنترل سفارشی را به شکل زیر به آن اضافه کنید.
1 2 3 4 5 6 7 8 9 10 11 12 13 | <Application x:Class="CustomRadioButtonDemo.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:,,,/CustomRadioButton;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 53 54 55 56 57 | <Window x:Class="CustomRadioButtonDemo.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:CustomRadioButton;assembly=CustomRadioButton" mc:Ignorable="d" Title="SourceSara.Com" Height="500" Width="600" WindowStartupLocation="CenterScreen" Background="#202C39" TextElement.FontFamily="Roboto"> <Window.Resources> <Style TargetType="{x:Type cmp:CustomRadioButton}"> <Setter Property="Margin" Value="8" /> <Setter Property="FontFamily" Value="Roboto" /> <Setter Property="Foreground" Value="#a0afc0" /> <Setter Property="BorderBrush" Value="#3C4C5E" /> <Setter Property="CheckedBrush" Value="#8ab4f8" /> <Setter Property="BulletSize" Value="18" /> </Style> </Window.Resources> <Viewbox Margin="6"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" FontSize="24" Margin="0,16" TextAlignment="Center" Foreground="#8ab4f8" Text="WPF Custom Radio Button"/> <StackPanel Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"> <cmp:CustomRadioButton IsChecked="True" GroupName="DemoGroup" Content="Hey, mark me!"/> <cmp:CustomRadioButton CheckedBrush="#f28b82" GroupName="DemoGroup" Content="No, please mark me!"/> <cmp:CustomRadioButton CheckedBrush="#b782f2" GroupName="DemoGroup" Content="Ignore them, just mark me!"/> </StackPanel> </Grid> </Viewbox> </Window> |
توجه! در بخش های زیر باید اسم پروژه WPF که ایجاد کرده اید را قرار دهید.
حال پروژه را Build و Run کنید تا نتیجه کار را مشاهده کنید.
هیچ نظری ثبت نشده است