첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
유용한 소스 코드가 있으면 icodebroker@naver.com으로 보내주시면 감사합니다.
블로그 자료는 자유롭게 사용하세요.

728x90
반응형

TestProject.zip
다운로드

▶ DatePicker.xaml

<UserControl x:Class="TestProject.DatePicker"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:g="clr-namespace:System.Globalization;assembly=mscorlib">
    <UserControl.Resources>
        <Style TargetType="{x:Type RepeatButton}">
            <Setter Property="Width"
                Value="{Binding RelativeSource={RelativeSource self}, Path=ActualHeight}" />
            <Setter Property="Focusable" Value="False"                                                             />
            <Style.Triggers>
                <DataTrigger
                    Binding="{Binding ElementName=notApplicableCheckBox, Path=IsChecked}"
                    Value="True">
                    <Setter Property="IsEnabled" Value="False" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
        <Style
            TargetType="{x:Type StatusBarItem}">
            <Setter Property="Margin"              Value="1"      />
            <Setter Property="HorizontalAlignment" Value="Center" />
            <Setter Property="VerticalAlignment"   Value="Center" />
        </Style>
        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="BorderThickness"            Value="1"           />
            <Setter Property="BorderBrush"                Value="Transparent" />
            <Setter Property="Margin"                     Value="1"           />
            <Setter Property="HorizontalContentAlignment" Value="Center"      />
            <Style.Triggers>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="IsSelected"                 Value="True"  />
                        <Condition Property="Selector.IsSelectionActive" Value="False" />
                    </MultiTrigger.Conditions>
                    <Setter
                        Property="BorderBrush"
                        Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
                </MultiTrigger>
                <DataTrigger
                    Binding="{Binding ElementName=notApplicableCheckBox, Path=IsChecked}"
                    Value="True">
                    <Setter Property="IsEnabled" Value="False" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </UserControl.Resources>
    <Border
        BorderThickness="1"
        BorderBrush="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Grid
                Background="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}"
                TextBlock.Foreground="{DynamicResource {x:Static SystemColors.ControlLightLightBrushKey}}">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*"    />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <RepeatButton Grid.Column="0"
                    FontWeight="Bold"
                    Content="◀"
                    Click="previousButon_Click" />
                <TextBlock Name="monthYearTextBlock" Grid.Column="1"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Margin="3" />
                <RepeatButton Grid.Column="2"
                    FontWeight="Bold"
                    Content="▶"
                    Click="nextButton_Click" />
            </Grid>
            <StatusBar Grid.Row="1"
                ItemsSource="{Binding Source={x:Static g:DateTimeFormatInfo.CurrentInfo},
                    Path=AbbreviatedDayNames}">
                <StatusBar.ItemsPanel>
                    <ItemsPanelTemplate>
                        <UniformGrid Rows="1" />
                    </ItemsPanelTemplate>
                </StatusBar.ItemsPanel>
            </StatusBar>
            <Border Grid.Row="2"
                BorderThickness="0 1 0 1"
                BorderBrush="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}">
                <ListBox Name="monthListBox"
                    SelectionChanged="monthListBox_SelectionChanged">
                    <ListBox.ItemsPanel>
                        <ItemsPanelTemplate>
                            <UniformGrid Name="monthGrid"
                                Background="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}"
                                Columns="7"
                                Rows="6"
                                IsItemsHost="True" />
                        </ItemsPanelTemplate>
                    </ListBox.ItemsPanel>
                    <ListBoxItem>dummy item</ListBoxItem>
                </ListBox>
            </Border>
            <CheckBox Name="notApplicableCheckBox" Grid.Row="3"
                Margin="3"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Checked="notApplicableCheckBox_Checked"
                Unchecked="notApplicableCheckBox_Unchecked">
                Not applicable
            </CheckBox>
        </Grid>
    </Border>
</UserControl>

 

▶ DatePicker.xaml.cs

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;

namespace TestProject
{
    /// <summary>
    /// 날짜 선택기
    /// </summary>
    public partial class DatePicker
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Routing Event
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 날짜 변경시 이벤트 - DateChangedEvent

        /// <summary>
        /// 날짜 변경시 이벤트
        /// </summary>
        public static readonly RoutedEvent DateChangedEvent = EventManager.RegisterRoutedEvent
        (
            "DateChanged",
            RoutingStrategy.Bubble,
            typeof(RoutedPropertyChangedEventHandler<DateTime?>),
            typeof(DatePicker)
        );

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 날짜 변경시 이벤트 - DateChanged

        /// <summary>
        /// 날짜 변경시 이벤트
        /// </summary>
        public event RoutedPropertyChangedEventHandler<DateTime?> DateChanged
        {
            add
            {
                AddHandler(DateChangedEvent, value);
            }
            remove
            {
                RemoveHandler(DateChangedEvent, value);
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 날짜 속성 - DateProperty

        /// <summary>
        /// 날짜 속성
        /// </summary>
        public static readonly DependencyProperty DateProperty = DependencyProperty.Register
        (
            "Date",
            typeof(DateTime?),
            typeof(DatePicker),
            new PropertyMetadata
            (
                new DateTime(),
                DatePropertyChangedCallback
            )
        );

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 월 그리드
        /// </summary>
        private UniformGrid monthGrid;

        /// <summary>
        /// 저장 날짜
        /// </summary>
        private DateTime saveDate = DateTime.Now.Date;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 날짜 - Date

        /// <summary>
        /// 날짜
        /// </summary>
        public DateTime? Date
        {
            set
            {
                SetValue(DateProperty, value);
            }
            get
            {
                return (DateTime?)GetValue(DateProperty);
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - DatePicker()

        /// <summary>
        /// 생성자
        /// </summary>
        public DatePicker()
        {
            InitializeComponent();

            Date = this.saveDate;

            Loaded += UserControl_Loaded;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 날짜 속성 변경시 콜백 처리하기 - DatePropertyChangedCallback(d, e)

        /// <summary>
        /// 날짜 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void DatePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as DatePicker).OnDateChanged((DateTime?)e.OldValue, (DateTime?)e.NewValue);
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Protected

        #region 프리뷰 키 다운시 처리하기 - OnPreviewKeyDown(e)

        /// <summary>
        /// 프리뷰 키 다운시 처리하기
        /// </summary>
        /// <param name="e">이벤트 인자</param>
        protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);

            if(e.Key == Key.PageDown)
            {
                FlipPage(true);

                e.Handled = true;
            }
            else if(e.Key == Key.PageUp)
            {
                FlipPage(false);

                e.Handled = false;
            }
        }

        #endregion
        #region 날짜 변경시 처리하기 - OnDateChanged(before, after)

        /// <summary>
        /// 일자 변경시 처리하기
        /// </summary>
        /// <param name="before">변경전 일시</param>
        /// <param name="after">변경후 일시</param>
        protected virtual void OnDateChanged(DateTime? before, DateTime? after)
        {
            this.notApplicableCheckBox.IsChecked = after == null;

            if(after != null)
            {
                DateTime afterValue = (DateTime)after;

                this.monthYearTextBlock.Text = afterValue.ToString(DateTimeFormatInfo.CurrentInfo.YearMonthPattern);

                if(this.monthGrid != null)
                {
                    this.monthGrid.FirstColumn = (int)(new DateTime(afterValue.Year, afterValue.Month, 1).DayOfWeek);
                }

                int dayCountInMonth = DateTime.DaysInMonth(afterValue.Year, afterValue.Month);

                if(dayCountInMonth != this.monthListBox.Items.Count)
                {
                    this.monthListBox.BeginInit();

                    this.monthListBox.Items.Clear();

                    for(int i = 0; i < dayCountInMonth;  i++)
                    {
                        this.monthListBox.Items.Add((i + 1).ToString());
                    }

                    this.monthListBox.EndInit();
                }

                this.monthListBox.SelectedIndex = afterValue.Day - 1;
            }

            RoutedPropertyChangedEventArgs<DateTime?> e = new RoutedPropertyChangedEventArgs<DateTime?>
            (
                before,
                after,
                DatePicker.DateChangedEvent
            );

            e.Source = this;

            RaiseEvent(e);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private
        ////////////////////////////////////////////////////////////////////// Event

        #region 사용자 컨트롤 로드시 처리하기 - UserControl_Loaded(sender, e)

        /// <summary>
        /// 사용자 컨트롤 로드시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            this.monthGrid = GetGrid(this.monthListBox);

            if(Date != null)
            {
                DateTime dateValue = (DateTime)Date;

                this.monthGrid.FirstColumn = (int)(new DateTime(dateValue.Year, dateValue.Month, 1).DayOfWeek);
            }
        }

        #endregion
        #region 이전 버튼 클릭시 처리하기 - previousButon_Click(sender, e)

        /// <summary>
        /// 이전 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void previousButon_Click(object sender, RoutedEventArgs e)
        {
            FlipPage(true);
        }

        #endregion
        #region 다음 버튼 클릭시 처리하기 - nextButton_Click(sender, e)

        /// <summary>
        /// 다음 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void nextButton_Click(object sender, RoutedEventArgs e)
        {
            FlipPage(false);
        }

        #endregion
        #region 월 리스트 박스 선택 변경시 처리하기 - monthListBox_SelectionChanged(sender, e)

        /// <summary>
        /// 월 리스트 박스 선택 변경시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void monthListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if(Date == null)
            {
                return;
            }

            DateTime dateValue = (DateTime)Date;

            if(this.monthListBox.SelectedIndex != -1)
            {
                Date = new DateTime
                (
                    dateValue.Year,
                    dateValue.Month,
                    int.Parse(this.monthListBox.SelectedItem as string)
                );
            }
        }

        #endregion
        #region Not Applicable 체크 박스 체크시 처리하기 - notApplicableCheckBox_Checked(sender, e)

        /// <summary>
        /// Not Applicable 체크 박스 체크시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void notApplicableCheckBox_Checked(object sender, RoutedEventArgs e)
        {
            if(Date != null)
            {
                this.saveDate = (DateTime)Date;

                Date = null;
            }
        }

        #endregion
        #region Not Applicable 체크 박스 체크 해제시 처리하기 - notApplicableCheckBox_Unchecked(sender, e)

        /// <summary>
        /// Not Applicable 체크 박스 체크 해제시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void notApplicableCheckBox_Unchecked(object sender, RoutedEventArgs e)
        {
            Date = this.saveDate;
        }

        #endregion

        ////////////////////////////////////////////////////////////////////// Function

        #region 그리드 구하기 - GetGrid(d)

        /// <summary>
        /// 그리드 구하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <returns>그리드</returns>
        private UniformGrid GetGrid(DependencyObject d)
        {
            if(d is UniformGrid)
            {
                return d as UniformGrid;
            }

            for(int i = 0; i < VisualTreeHelper.GetChildrenCount(d); i++)
            {
                Visual visual = GetGrid(VisualTreeHelper.GetChild(d, i));

                if(visual != null)
                {
                    return visual as UniformGrid;
                }
            }

            return null;
        }

        #endregion
        #region 페이지 플립하기 - FlipPage(isPrevious)

        /// <summary>
        /// 페이지 플립하기
        /// </summary>
        /// <param name="isPrevious">이전 여부</param>
        private void FlipPage(bool isPrevious)
        {
            if(Date == null)
            {
                return;
            }

            DateTime dateValue = (DateTime)Date;

            int pageNumber = isPrevious ? -1 : 1;

            if(Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
            {
                pageNumber *= 12;
            }

            if(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
            {
                pageNumber = Math.Max(-1200, Math.Min(1200, 120 * pageNumber));
            }

            int year  = dateValue.Year  + pageNumber / 12;
            int month = dateValue.Month + pageNumber % 12;

            while(month < 1)
            {
                month += 12;
                year  -= 1;
            }

            while(month > 12)
            {
                month -= 12;
                year  += 1;
            }

            if(year < DateTime.MinValue.Year)
            {
                Date = DateTime.MinValue.Date;
            }
            else if(year > DateTime.MaxValue.Year)
            {
                Date = DateTime.MaxValue.Date;
            }
            else
            {
                Date = new DateTime
                (
                    year,
                    month,
                    Math.Min(dateValue.Day, DateTime.DaysInMonth(year, month))
                );
            }
        }

        #endregion
    }
}

 

▶ MainWindow.xaml

<Window x:Class="TestProject.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TestProject"
    Width="800"
    Height="600"
    Title="날짜 선택기 만들기"
    FontFamily="나눔고딕코딩"
    FontSize="16">
    <Grid>
        <StackPanel
            HorizontalAlignment="Center"
            VerticalAlignment="Center">
            <local:DatePicker x:Name="datePicker"
                Margin="10"
                HorizontalAlignment="Center"
                DateChanged="datePicker_DateChanged" />
            <StackPanel
                Margin="10"
                Orientation="Horizontal">
                <TextBlock Text="바인딩 값 : " />
                <TextBlock Text="{Binding ElementName=datePicker, Path=Date}" />
            </StackPanel>
            <StackPanel
                Margin="10"
                Orientation="Horizontal">
                <TextBlock Text="이벤트 핸들러 값 : " />
                <TextBlock Name="dateTextBlock" />
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

 

▶ MainWindow.cs

using System;
using System.Windows;

namespace TestProject
{
    /// <summary>
    /// 메인 윈도우
    /// </summary>
    public partial class MainWindow : Window
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - MainWindow()

        /// <summary>
        /// 생성자
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 날짜 선택기 일자 변경시 처리하기 - datePicker_DateChanged(sender, e)

        /// <summary>
        /// 날짜 선택기 일자 변경시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void datePicker_DateChanged(object sender, RoutedPropertyChangedEventArgs<DateTime?> e)
        {
            if(e.NewValue != null)
            {
                DateTime dateTime = (DateTime)e.NewValue;

                this.dateTextBlock.Text = dateTime.ToString("d");
            }
            else
            {
                this.dateTextBlock.Text = string.Empty;
            }
        }

        #endregion
    }
}
728x90
반응형
Posted by 사용자 icodebroker
TAG , ,

댓글을 달아 주세요