WPF için ProgressRing

WPF üzerinde Windows 8’dekine benzer aktiviteyi gösteren bir kontrol oluşturabiliriz.

Not: MahApps.Metro kütüphanesi bu kontrolü ve daha bir çok farklı kontrolü içermektedir. Bunun yanında tema desteği de mevcuttur.
http://mahapps.com/

Kullanımı:
ProgressRing.cs dosyasını Controls klasörü altında oluşturulur.
ProgressRing.xaml dosyasını Styles altında oluşturulur.
App.xaml dosyasında Application.Resources içine ProgressRing.xaml resource dictionary olarak eklenir.
Kullanacağımız sayfada namespace tanımlanarak kontrol kullanılabilir.

ProgressRing.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace ProgressRingTest.Controls
{
    [TemplateVisualState(Name = "Large", GroupName = "SizeStates")]
    [TemplateVisualState(Name = "Small", GroupName = "SizeStates")]
    [TemplateVisualState(Name = "Inactive", GroupName = "ActiveStates")]
    [TemplateVisualState(Name = "Active", GroupName = "ActiveStates")]
    public class ProgressRing : Control
    {
        public static readonly DependencyProperty BindableWidthProperty = DependencyProperty.Register("BindableWidth", typeof(double), typeof(ProgressRing), new PropertyMetadata(default(double), BindableWidthCallback));

        public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("IsActive", typeof(bool), typeof(ProgressRing), new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, IsActiveChanged));

        public static readonly DependencyProperty IsLargeProperty = DependencyProperty.Register("IsLarge", typeof(bool), typeof(ProgressRing), new PropertyMetadata(true, IsLargeChangedCallback));

        public static readonly DependencyProperty MaxSideLengthProperty = DependencyProperty.Register("MaxSideLength", typeof(double), typeof(ProgressRing), new PropertyMetadata(default(double)));

        public static readonly DependencyProperty EllipseDiameterProperty = DependencyProperty.Register("EllipseDiameter", typeof(double), typeof(ProgressRing), new PropertyMetadata(default(double)));

        public static readonly DependencyProperty EllipseOffsetProperty = DependencyProperty.Register("EllipseOffset", typeof(Thickness), typeof(ProgressRing), new PropertyMetadata(default(Thickness)));

        private List<Action> _deferredActions = new List<Action>();

        static ProgressRing()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ProgressRing), new FrameworkPropertyMetadata(typeof(ProgressRing)));
        }

        public ProgressRing()
        {
            SizeChanged += OnSizeChanged;
        }

        public double MaxSideLength
        {
            get { return (double)GetValue(MaxSideLengthProperty); }
            private set { SetValue(MaxSideLengthProperty, value); }
        }

        public double EllipseDiameter
        {
            get { return (double)GetValue(EllipseDiameterProperty); }
            private set { SetValue(EllipseDiameterProperty, value); }
        }

        public Thickness EllipseOffset
        {
            get { return (Thickness)GetValue(EllipseOffsetProperty); }
            private set { SetValue(EllipseOffsetProperty, value); }
        }

        public double BindableWidth
        {
            get { return (double)GetValue(BindableWidthProperty); }
            private set { SetValue(BindableWidthProperty, value); }
        }

        public bool IsActive
        {
            get { return (bool)GetValue(IsActiveProperty); }
            set { SetValue(IsActiveProperty, value); }
        }

        public bool IsLarge
        {
            get { return (bool)GetValue(IsLargeProperty); }
            set { SetValue(IsLargeProperty, value); }
        }

        private static void BindableWidthCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var ring = dependencyObject as ProgressRing;
            if (ring == null)
                return;

            var action = new Action(() =>
            {
                ring.SetEllipseDiameter(
                    (double)dependencyPropertyChangedEventArgs.NewValue);
                ring.SetEllipseOffset(
                    (double)dependencyPropertyChangedEventArgs.NewValue);
                ring.SetMaxSideLength(
                    (double)dependencyPropertyChangedEventArgs.NewValue);
            });

            if (ring._deferredActions != null)
                ring._deferredActions.Add(action);
            else
                action();
        }

        private void SetMaxSideLength(double width)
        {
            MaxSideLength = width <= 20 ? 20 : width; } private void SetEllipseDiameter(double width) { EllipseDiameter = width / 8; } private void SetEllipseOffset(double width) { EllipseOffset = new Thickness(0, width / 2, 0, 0); } private static void IsLargeChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { var ring = dependencyObject as ProgressRing; if (ring == null) return; ring.UpdateLargeState(); } private void UpdateLargeState() { Action action; if (IsLarge) action = () => VisualStateManager.GoToState(this, "Large", true);
            else
                action = () => VisualStateManager.GoToState(this, "Small", true);

            if (_deferredActions != null)
                _deferredActions.Add(action);

            else
                action();
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
        {
            BindableWidth = ActualWidth;
        }

        private static void IsActiveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var ring = dependencyObject as ProgressRing;
            if (ring == null)
                return;

            ring.UpdateActiveState();
        }

        private void UpdateActiveState()
        {
            Action action;

            if (IsActive)
                action = () => VisualStateManager.GoToState(this, "Active", true);
            else
                action = () => VisualStateManager.GoToState(this, "Inactive", true);

            if (_deferredActions != null)
                _deferredActions.Add(action);

            else
                action();
        }

        public override void OnApplyTemplate()
        {
            //make sure the states get updated
            UpdateLargeState();
            UpdateActiveState();
            base.OnApplyTemplate();
            if (_deferredActions != null)
                foreach (var action in _deferredActions)
                    action();
            _deferredActions = null;
        }
    }

    internal class WidthToMaxSideLengthConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is double)
            {
                var width = (double)value;
                return width <= 20 ? 20 : width;
            }

            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

ProgressRing.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:ProgressRingTest.Controls">

<Style TargetType="controls:ProgressRing">
        <Setter Property="Foreground" Value="{DynamicResource AccentColorBrush}" />
        <Setter Property="IsHitTestVisible" Value="False" />
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="MinHeight" Value="20" />
        <Setter Property="MinWidth" Value="20" />
        <Setter Property="Height" Value="60" />
        <Setter Property="Width" Value="60" />
        <Setter Property="IsTabStop" Value="False" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="controls:ProgressRing">
                    <Border Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
                        <Border.Resources>

<Style x:Key="ProgressRingEllipseStyle" TargetType="Ellipse">
                                <Setter Property="Opacity" Value="0" />
                                <Setter Property="HorizontalAlignment" Value="Left" />
                                <Setter Property="VerticalAlignment" Value="Top" />
                            </Style>


                        </Border.Resources>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="SizeStates">
                                <VisualState x:Name="Large">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="SixthCircle" Storyboard.TargetProperty="Visibility">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Small" />
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="ActiveStates">
                                <VisualState x:Name="Inactive" />
                                <VisualState x:Name="Active">
                                    <Storyboard RepeatBehavior="Forever">
                                        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="Ring" Storyboard.TargetProperty="Visibility">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E1" Storyboard.TargetProperty="Opacity" BeginTime="0">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E2" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.167">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E3" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.334">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E4" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.501">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E5" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.668">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E6" Storyboard.TargetProperty="Opacity" BeginTime="00:00:00.835">
                                            <DiscreteDoubleKeyFrame KeyTime="0" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.21" Value="1" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.22" Value="0" />
                                            <DiscreteDoubleKeyFrame KeyTime="0:0:3.47" Value="0" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E1R" BeginTime="0" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="-110" KeySpline="0.13,0.21,0.1,0.7" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="10" KeySpline="0.02,0.33,0.38,0.77" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="93" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="205" KeySpline="0.57,0.17,0.95,0.75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="357" KeySpline="0,0.19,0.07,0.72" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="439" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="585" KeySpline="0,0,0.95,0.37" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E2R" BeginTime="00:00:00.167" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="-116" KeySpline="0.13,0.21,0.1,0.7" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="4" KeySpline="0.02,0.33,0.38,0.77" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="87" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="199" KeySpline="0.57,0.17,0.95,0.75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="351" KeySpline="0,0.19,0.07,0.72" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="433" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="579" KeySpline="0,0,0.95,0.37" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E3R" BeginTime="00:00:00.334" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="-122" KeySpline="0.13,0.21,0.1,0.7" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-2" KeySpline="0.02,0.33,0.38,0.77" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="81" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="193" KeySpline="0.57,0.17,0.95,0.75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="345" KeySpline="0,0.19,0.07,0.72" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="427" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="573" KeySpline="0,0,0.95,0.37" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E4R" BeginTime="00:00:00.501" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="-128" KeySpline="0.13,0.21,0.1,0.7" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-8" KeySpline="0.02,0.33,0.38,0.77" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="187" KeySpline="0.57,0.17,0.95,0.75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="339" KeySpline="0,0.19,0.07,0.72" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="421" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="567" KeySpline="0,0,0.95,0.37" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E5R" BeginTime="00:00:00.668" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="-134" KeySpline="0.13,0.21,0.1,0.7" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-14" KeySpline="0.02,0.33,0.38,0.77" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="69" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="181" KeySpline="0.57,0.17,0.95,0.75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="331" KeySpline="0,0.19,0.07,0.72" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="415" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="561" KeySpline="0,0,0.95,0.37" />
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="E6R" BeginTime="00:00:00.835" Storyboard.TargetProperty="Angle">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="-140" KeySpline="0.13,0.21,0.1,0.7" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:0.433" Value="-20" KeySpline="0.02,0.33,0.38,0.77" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.2" Value="63" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:1.617" Value="175" KeySpline="0.57,0.17,0.95,0.75" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.017" Value="325" KeySpline="0,0.19,0.07,0.72" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:2.783" Value="409" />
                                            <SplineDoubleKeyFrame KeyTime="0:0:3.217" Value="555" KeySpline="0,0,0.95,0.37" />
                                        </DoubleAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Grid x:Name="Ring" Margin="{TemplateBinding Padding}" MaxWidth="{Binding MaxSideLength, RelativeSource={RelativeSource Mode=TemplatedParent}}" MaxHeight="{Binding MaxSideLength, RelativeSource={RelativeSource Mode=TemplatedParent}}" Visibility="Collapsed" RenderTransformOrigin=".5,.5" FlowDirection="LeftToRight">

                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E1R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E1" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" Fill="{TemplateBinding Foreground}" />
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E2R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E2" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" Fill="{TemplateBinding Foreground}" />
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E3R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E3" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" Fill="{TemplateBinding Foreground}" />
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E4R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E4" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" Fill="{TemplateBinding Foreground}" />
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E5R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E5" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" Fill="{TemplateBinding Foreground}" />
                            </Canvas>
                            <Canvas RenderTransformOrigin=".5,.5" Visibility="Collapsed" x:Name="SixthCircle">
                                <Canvas.RenderTransform>
                                    <RotateTransform x:Name="E6R" />
                                </Canvas.RenderTransform>
                                <Ellipse x:Name="E6" Style="{StaticResource ProgressRingEllipseStyle}" Width="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Height="{Binding EllipseDiameter, RelativeSource={RelativeSource Mode=TemplatedParent}}" Margin="{Binding EllipseOffset, RelativeSource={RelativeSource Mode=TemplatedParent}}" Fill="{TemplateBinding Foreground}" />
                            </Canvas>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

 

WPF’de Data Annotations ile Veri Doğrulaması

Ekrandan veri girişi yapılan uygulamalarda verilerin doğrulanması kaçınılmazdır. WPF üzerinde bu işi kolay bir yoldan halletmek için .NET’teki “System.ComponentModel.DataAnnotations” kütüphanesini kullanacağız. Öncelikle ekrana alanları ve gönder tuşunu ekliyoruz. Ekranın arka tarafında “Datacontext”‘i ayarlayarak ekranı daha önce oluşturduğumuz modele bağlıyoruz. Ekrandaki alanları da “Binding” yardımı ile modeldeki “Property”‘lere bağlıyoruz. Alanların kısıtlarını “Property”‘lerin üzerinde tanımladığımız “Data Annotations” ile belirliyoruz. “Submit” tuşuna basıldığında ise yazdığımız kodla doğrulama gerçekleşiyor ve mesaj kutusunda gösteriliyor.

MainWindow.xaml

<Window x:Class="WpfValidation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="150"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        
        <TextBlock Text="Name:" Grid.Column="0" Grid.Row="0"/>
        <TextBox Text="{Binding Name}" Grid.Column="1" Grid.Row="0"/>

        <TextBlock Text="Age:" Grid.Column="0" Grid.Row="1"/>
        <TextBox Text="{Binding Age}" Grid.Column="1" Grid.Row="1"/>
        
        <TextBlock Text="Birth Date:" Grid.Column="0" Grid.Row="2"/>
        <TextBox Text="{Binding BirthDate}" Grid.Column="1" Grid.Row="2"/>

        <TextBlock Text="TCKN:" Grid.Column="0" Grid.Row="3"/>
        <TextBox Text="{Binding Tckn}" Grid.Column="1" Grid.Row="3"/>
        
        <Button Content="Submit" Grid.Column="0" Grid.Row="4" Grid.ColumnSpan="2" Click="Button_Click"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Windows;
using WpfValidation.ViewModels;

namespace WpfValidation
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new MainWindowViewModel();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var validationResults = new List<ValidationResult>();

            bool isValid = Validator.TryValidateObject(DataContext,
                                                       new ValidationContext(DataContext), 
                                                       validationResults, 
                                                       true);

            if (isValid)
            {
                MessageBox.Show("Validation success.");
            }
            else
            {
                var strErr = String.Empty;

                foreach (var item in validationResults)
                {
                    strErr += item.ErrorMessage + "\n";
                }

                MessageBox.Show(strErr);
            }
        }
    }
}

MainWindowViewModel.cs

using System;
using System.ComponentModel.DataAnnotations;

namespace WpfValidation.ViewModels
{
    public class MainWindowViewModel
    {
        public MainWindowViewModel()
        {
            // Default values
            Name = "Test";
            Age = 67;
            BirthDate = new DateTime(1980, 02, 10);
            Tckn = "11009797396";
        }

        [Required(ErrorMessage = "Name is required.")]
        [StringLength(10, MinimumLength = 3, ErrorMessage = "The name must be between 3 and 10 characters long.")]
        [RegularExpression(@"^[a-zA-Z]+$", ErrorMessage = "The name must only contain letters (a-z, A-Z).")]
        public string Name { get; set; }

        public int Age { get; set; }

        public DateTime BirthDate { get; set; }

        [Required(ErrorMessage = "Tckn is required.")]
        [StringLength(11, MinimumLength = 11, ErrorMessage = "The tckn must be 11 characters long.")]
        public string Tckn { get; set; }
    }
}

Xaml içinde StringFormat Türkçeleştirme

Xaml içinden StringFormat ile herhangi bir tarihin hangi güne denk geldiğini göstermek istediğimizde ingilizce olarak gösterildiğini göreceksiniz. Bunun nedeni xaml’in varsayılan dil olarak en-US kullanmasıdır.

<TextBlock Text="{Binding Path=Tarih, StringFormat=dd.MM.yyyy - dddd}"/>

Bu problemi aşmak için aynı xaml’de namespace’lerin tanımlandığı yere aşağıdaki kodu eklemek yeterlidir.

xml:lang="tr-TR"

Async ve Await ile Asenkron Programlama

Performans dar-boğazlarını önlemek ve genel uygulama duyarlılığını arttırmak için asenkron programlama kullanabiliriz. Ancak, asenkron uygulamalar yazmak için kullanılan geleneksel teknikler, uygulamaları, kodlama, hata ayıklama ve bakım yönünden daha karmaşık hale getirebilir.

Visual Studio 2012, .NET Framework 4.5 ve Windows Runtime ile asenkron programlar yazmak oldukça basitleşti. async ve await niteleyicileri ile derleyici, geliştiricinin yapması gereken zor işleri üzerine aldı ve uygulama senkron kod benzeri mantıksal yapısını koruyabildi. Sonuç olarak, daha az çaba ile asenkron programlamanın tüm avantajlarından faydalanabiliyoruz.

using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace WpfApplication
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            LongRunningProcessAsync();

            MessageBox.Show("Started.");
        }

        private async void LongRunningProcessAsync()
        {
            await Task.Run(() =>
            {
                for (int i = 0; i < 5000; i++)
                {
                    Thread.Sleep(1);
                }

                MessageBox.Show("Finished!");
            });

            MessageBox.Show("Awaited message.");
        }
    }
}
<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="150" Width="525">
    <Grid>
        <ProgressBar HorizontalAlignment="Left" Height="10" Margin="8,8,0,0" VerticalAlignment="Top" Width="493" IsIndeterminate="True"/>
        <Button Content="Start" HorizontalAlignment="Left" Margin="200,72,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
    </Grid>
</Window>

Kontrolün Özelliğini Properties.Settings’e Bağlamak

Kontrolün herhangi bir özelliğini Properties.Settings’e bağlayarak, az kod ile çok iş yapmak mümkün. Aşağıdaki örnekte CheckBox’ın IsChecked özeliği Properties.Settings’te tanımlı Bool1’e, TextBox’ın Text özelliği ise yine Properties.Settings’te tanımlı String1’e bağlanmıştır. Böylece otomatik olarak değişimler birbirine yansıyacaktır.

<Window x:Class="WpfApplication1.MainWindow"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:Properties="clr-namespace:WpfApplication1.Properties"
   Title="MainWindow" Height="350" Width="525">
   <Grid>
      <CheckBox Content="CheckBox" Height="16" HorizontalAlignment="Left" Margin="12,12,0,0" Name="checkBox1" VerticalAlignment="Top" IsChecked="{Binding Path=Bool1, Source={x:Static Properties:Settings.Default}}" />
      <TextBox Height="23" HorizontalAlignment="Left" Margin="12,34,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=String1, Source={x:Static Properties:Settings.Default}}" />
   </Grid>
</Window>

String Format Kodları

Sayılar:
Specifier Type Format Output (Passed Double 1.42) Output (Passed Int -12400)
c Currency {0:c} $1.42 -$12,400
d Decimal (Whole number) {0:d} System.FormatException -12400
e Scientific {0:e} 1.420000e+000 -1.240000e+004
f Fixed point {0:f} 1.42 -12400.00
g General {0:g} 1.42 -12400
n Number with commas for thousands {0:n} 1.42 -12,400
r Round trippable {0:r} 1.42 System.FormatException
x Hexadecimal {0:x4} System.FormatException cf90

Özel Sayı Biçimlendirme:
Specifier Type Example Output (Passed Double 1500.42) Note
0 Zero placeholder {0:00.0000} 1500.4200 Pads with zeroes.
# Digit placeholder {0:(#).##} (1500).42
. Decimal point {0:0.0} 1500.4
, Thousand separator {0:0,0} 1,500 Must be between two zeroes.
,. Number scaling {0:0,.} 2 Comma adjacent to Period scales by 1000.
% Percent {0:0%} 150042% Multiplies by 100, adds % sign.
e Exponent placeholder {0:00e+0} 15e+2 Many exponent formats available.

Tarihler:
Specifier Type Example (Passed System.DateTime.Now)
d Short date 10/12/2002
D Long date December 10, 2002
t Short time 10:11 PM
T Long time 10:11:29 PM
f Full date & time December 10, 2002 10:11 PM
F Full date & time (long) December 10, 2002 10:11:29 PM
g Default date & time 10/12/2002 10:11 PM
G Default date & time (long) 10/12/2002 10:11:29 PM
M Month day pattern December 10
r RFC1123 date string Tue, 10 Dec 2002 22:11:29 GMT
s Sortable date string 2002-12-10T22:11:29
u Universal sortable, local time 2002-12-10 22:13:50Z
U Universal sortable, GMT December 11, 2002 3:13:50 AM
Y Year month pattern December, 2002

Özel Tarih Biçimlendirme:
Specifier Type Example Example Output
dd Day {0:dd} 10
ddd Day name {0:ddd} Tue
dddd Full day name {0:dddd} Tuesday
f, ff, … Second fractions {0:fff} 932
gg, … Era {0:gg} A.D.
hh 2 digit hour {0:hh} 10
HH 2 digit hour, 24hr format {0:HH} 22
mm Minute 00-59 {0:mm} 38
MM Month 01-12 {0:MM} 12
MMM Month abbreviation {0:MMM} Dec
MMMM Full month name {0:MMMM} December
ss Seconds 00-59 {0:ss} 46
tt AM or PM {0:tt} PM
yy Year, 2 digits {0:yy} 02
yyyy Year {0:yyyy} 2002
zz Timezone offset, 2 digits {0:zz} -05
zzz Full timezone offset {0:zzz} -05:00
: Separator {0:hh:mm:ss} 10:43:20
/ Separator {0:dd/MM/yyyy} 10/12/2002

Numaralandırmalar:
Specifier Type
g Default (Flag names if available, otherwise decimal)
f Flags always
d Integer always
x Eight digit hex.

Bazı Faydalı Örnekler:
String.Format(“{0:(###) ###-####}”, 8005551212); => (800) 555-1212

BusyIndicator Kullanımı

Extended WPF Toolkit içinde işe yarayan birçok kontrol var. BusyIndicator kontrolü bunlardan sadece biri. Aşağıdaki gibi kullanılabilir.

<Grid>
   <extToolkit:BusyIndicator x:Name="_busyIndicator">
      <Button Click="Button_Click">Start Process</Button>
   </extToolkit:BusyIndicator>
</Grid>
private void Button_Click(object sender, RoutedEventArgs e)
{
   BackgroundWorker bw = new BackgroundWorker();
   bw.DoWork += (o, ea) =>
   {
      Thread.Sleep(3000);
   };
   bw.RunWorkerCompleted += (o, ea) =>
   {
      _busyIndicator.IsBusy = false;
   };
   _busyIndicator.IsBusy = true;
   bw.RunWorkerAsync();
}

MultiBinding ve StringFormat

.NET Framework 3.5 SP1 ile gelen özelliklerden biri, ilişkili değerleri kolay biçimlendirmeyi sağlamak için, {{Binding}} ifadeler içinde kullanılabilen StringFormat desteğidir. MultiBinding ile birlikte kullanımı ile ilgili örnek aşağıdadır. Bu örnekte TextBlock’un Text özelliği Name ve Surname adlı iki özelliğe birden bağlanmıştır.

<TextBlock>
   <TextBlock.Text>
      <MultiBinding StringFormat="{}{0} {1}">
         <Binding Path="Name" />
         <Binding Path="Surname" />
      </MultiBinding>
   </TextBlock.Text>
</TextBlock>