Calendar 날짜 기간설정기능 질문

안녕하세요 WPF로 기간설정기능을 만들려고하는데, 아래 그림처럼 시작날짜와 종료날짜를 캘린더에서 클릭하면 자동으로 해당 구간이 선택되도록 구현을 하고 싶은데요, 검색을 해봐도 그냥 기본기능만 설명하고 좀 심화적인 내용 설명을 못찾겠더라구요… SelectionMode라는게 있긴한데 제가 생각하는것과 다르게 동작하는거같고… 아래 그림처럼 구현하려면 어떻게해야할까요??

3 Likes

c# - WPF Calendar - Highlights Dates of Commitments - Stack Overflow
c# - Calendar Control - Highlight Dates Programmatically - Stack Overflow
How do I create a WPF Rounded Corner container? - Stack Overflow

비슷 하게는 나올수 있으나
커스텀 하게 만들어야 이쁘게 나오겠네요

3 Likes

이것 어려운 컨트롤 알고있습니다. 저도 예제가 있다면 키핑하고 싶군요
아마 실력자분도 각잡고 만드셔야 할것예요

3 Likes

디자인 적인 요소가 아닌 선택 액션을 드래그가 아닌 시작 날짜 클릭=> 종료 날자 클릭으로 하고 싶으신 건가요?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

public class ClickSelectionCalendar : Calendar
{
    private DateTime _startDate;
    private bool _isSelectionMode;

    public ClickSelectionCalendar()
    {
        Loaded += Handler_Loaded;
        SelectionMode = CalendarSelectionMode.SingleRange;
    }

    private void Handler_Loaded(object sender, RoutedEventArgs e)
    {
        Loaded -= Handler_Loaded;

        var buttons = FindVisualChildren<CalendarDayButton>(this).ToArray();

        foreach (var button in buttons)
        {
            button.PreviewMouseLeftButtonDown += Handler_ButtonPreviewMouseLeftButtonDown;
        }
    }

    private void Handler_ButtonPreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        var button = sender as CalendarDayButton;
        var date = (DateTime)button.DataContext;
        if (_isSelectionMode)
        {
            SelectedDates.Clear();
            SelectedDates.AddRange(_startDate, date);
            _isSelectionMode = false;
            e.Handled = true;
        }
        else
        { 
            _isSelectionMode = true;
            _startDate = date;
        }
    }

    public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj)
    {
        if (depObj != null)
        {
            var count = VisualTreeHelper.GetChildrenCount(depObj);

            for (var i = 0; i < count; i++)
            {
                object child = VisualTreeHelper.GetChild(depObj, i);
                if (child != null && child is T)
                {
                    yield return (T)child;
                }

                foreach (var childOfChild in FindVisualChildren<T>(child as DependencyObject))
                {
                    yield return childOfChild;
                }
            }
        }
    }
}
3 Likes

네, 드래그가 아닌 위 그림처럼 17일 클릭, 26일 클릭 하면 자동으로 17~26일 기간이 선택되게끔 처리하고 시작일과 종료일에 하이라이트를 주고싶은데 이 기능을 다 넣으려면 단일 캘린더 컨트롤로는 아무리 건드려봐도 잘 안되네요…

2 Likes

디자인도 중요한데 지금은 기능적인 측면을 먼저 구현하는데 초점을 두고있습니다!
공유해주신 링크는 나중에 디자인할때 참고하도록 하겠습니다 감사합니다!

1 Like

(주시 중…)

2 Likes

대충 간단하게…

더 디테일한 기능은 지선생님께…

// MainWindow.xaml
    xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:local="clr-namespace:WpfApp1"

    <Grid>
        <Calendar IsTodayHighlighted="False" SelectionMode="MultipleRange">
            <b:Interaction.Behaviors>
                <local:CalendarBehavior />
            </b:Interaction.Behaviors>
        </Calendar>
    </Grid>

// CalendarBehavior.cs
public class CalendarBehavior : Behavior<Calendar>
{
	private DateTime? Start = null;
	protected override void OnAttached()
	{
		base.OnAttached();
		AssociatedObject.SelectedDatesChanged += AssociatedObject_SelectedDatesChanged;
	}
	protected override void OnDetaching()
	{
		base.OnDetaching();
		AssociatedObject.SelectedDatesChanged -= AssociatedObject_SelectedDatesChanged;
	}

	private void AssociatedObject_SelectedDatesChanged(object? sender, SelectionChangedEventArgs e)
	{
		if (e.AddedItems.Count > 1)
		{
			e.Handled = true;
			return;
		}
		if (Start == null)
		{
			Start = (DateTime)e.AddedItems[0]!;
			return;
		}

		AssociatedObject.SelectedDates.AddRange(Start.Value, (DateTime)e.AddedItems[0]!);
		Start = null;
	}
}

Microsoft.Xaml.Behaviors.Wpf nuget 추가 했습니다.

3 Likes

G선생 부르고 신이라…불러야겠군요…

2 Likes

요건 제가 짰습니다 ㅎㅎ

2 Likes

아! 잘못 글을읽었네요…하하…

2 Likes

지금 확인했는데, 카테고리가 Windows Forms로 되어있으시네요.

image

WPF 카테고리로 변경하겠습니다.

2 Likes

WPF 를 기초만 해봤고, 현재는 블레이저를 주력으로 하고 있습니다.

블레이저에서 비슷한 양상으로 드래그로 처리한 적이 있습니다.

물론, 달력 요소를 별도로 정의했습니다. 대략적으로 설명드리면, 선택 모드인 경우,

드래깅 중임을 인식하기 위해, 모든 날짜 셀에 마우스 다운 이벤트와 마우스 오버 이벤트를,
드래깅 완료를 인식하기 위해, 날짜 셀을 담고 있는 컨테이너에 마우스 업/아웃 이벤트를 걸었습니다.

각 셀에서 마우스 이벤트가 발생하면, 각 셀이 나타내는 숫자를 별도의 이벤트 관리자에게 전송합니다.

이를 응용해보면,

셀의 이벤트를 수신한 관리자는 셀이 보내온 날짜(숫자)를 목록이나, 32비트 플래그로 만들어, PeriodChanged 이벤트를 통해 다시 모든 셀에 전파합니다.
당연히, 모든 셀은 이 이벤트를 구독해야겠지요.

class PeriodSelectionManager
{
   bool _selecting = false;
   public bool IsPeriodSelecting 
   {
      get => _selecting;
      set { _selecting = value; if(value is false) _days.Clear(); }
   }
   public event EventHandler<List<int>> PeriodChanged;

   List<int> _days = new();

   public void Toggle(int day)
   {
      if (_selecting is false) return;
      if (days.Contains(day) _days.Remove(day);
      else _days.Add(day);
      PeriodChanged?.Invoke(_days);
   }   
}

모든 셀은 PeriodChanged 이벤트를 구독하고, 핸들러 내부에서, EventArgs 값을 통해 자신의 위치를 파악하여 모양을 결정하면 될 것 같습니다.

if (목록에 자신의 숫자가 포함되어 있지 않다면) => return;

if ( 목록의 요소가 하나) => 동그라미
else if (자신이 목록의 처음) => 동그라미에 우측 박스,
else if (자신이 목록의 중간) => 동그라미 없는 박스
else (자신이 목록의 마지막) => 동그라미에 좌측 박스,

블레이저는 HTML 과 CSS 를 사용하기 때문에, 요소에 if 문을 거는 게 매우 쉬우나, XAML 로 하려면, 쎌을 커스텀 콘트롤로 만들고, 그 내부에 동그라미, 좌측 박스, 우측 박스를 두고, 위치에 때라 각 요소의 칼라 속성을 통해 가시성을 결정하면 될 것 같습니다.

3 Likes

카테고리 실수를… 감사합니다!

1 Like

흐 어렵군요… 답글감사드립니다 ㅠㅠ

2 Likes

일단 지금은 급해서 단일캘린더말고 그냥 캘린더 두개로 selection mode = “singleSelection” 설정해서 구현은 해놨는데 요거 한번 해보겠습니다… 감사합니다!

1 Like