Search code examples
c#wpfcalendarwrappanel

How to display days in calendar properly using wrapPanels?


I am writing a WPF application. I have a Calendar and Buttons for previous (<), current (today) and next (>) month. My problem is, when I switch a month for next or previous, first day of each month starts always on the same day.

I have a panel where I create other panels. Each panel represents one day. My code should put a label with the number of day in a correct position, but it always starts from the first WrapPanel. Where is the problem? Some screenshots below.

enter image description here After clicking next Month Button, February should start in Thursday. enter image description here

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace ActivityMonitor
{
/// <summary>
/// Logika interakcji dla klasy CalendarWindow.xaml
/// </summary>
public partial class CalendarWindow : Window
{
    //lista dni w danym miesiacu
    private List<WrapPanel> daysList = new List<WrapPanel>();

   //aktualna data
    private DateTime currentDate = DateTime.Today;

    public CalendarWindow()
    {
        InitializeComponent();
        DisplayCalendar();
    }

    //metoda wyświetlająca kalendarz
    private void DisplayCalendar()  
    {
        GenerateDayPanel(42);
        //AddDayLabelToWrap(GetFirstDayOfCurrentDate(), GetTotalDaysOfCurrentDate());
        DisplayCurrentDate();
    }

    //metoda 
    private int GetFirstDayOfCurrentDate()
    {
        DateTime firstDayOfMonth = new DateTime(currentDate.Year, currentDate.Month, 1);
        return (int) firstDayOfMonth.DayOfWeek + 1;
    }

    private int GetTotalDaysOfCurrentDate()
    {
        DateTime firstDayOfCurrentDate = new DateTime(currentDate.Year, currentDate.Month, 1);
        return firstDayOfCurrentDate.AddMonths(1).AddDays(-1).Day;
    }

    private void DisplayCurrentDate()
    {
        labelMonthAndYear.Content = currentDate.ToString("MMMM, yyyy");
        AddDayLabelToWrap(GetFirstDayOfCurrentDate(), GetTotalDaysOfCurrentDate());

    }

    //metoda ustawuająca miesiąc na poprzedni
    private void PreviousMonth()
    {
        currentDate = currentDate.AddMonths(-1);
        DisplayCurrentDate();
    }

    //metoda ustawiająca miesiąc na następny
    private void NextMonth()
    {
        currentDate = currentDate.AddMonths(1);
        DisplayCurrentDate();
    }

    //metoda ustawiająca miesiąc na aktualny
    private void Today()
    {
        currentDate = DateTime.Today;
        DisplayCurrentDate();
    }

    //metoda generująca dni tygodnia danego miesiąca
    private void GenerateDayPanel(int totalDays)
    {
        daysPanel.Children.Clear();
        daysList.Clear();
        for (int i = 1; i <= totalDays; i++)
        {
            var wrap = new WrapPanel();
            wrap.Name = $"wrap{i}";
            wrap.ItemWidth = 200;
            wrap.ItemHeight = 100;
            if(i%2 == 0)
            {
                wrap.Background = new SolidColorBrush(Colors.LightBlue);
            }
            else
            {
                wrap.Background = new SolidColorBrush(Colors.Gray);
            }
            daysPanel.Children.Add(wrap);
            daysList.Add(wrap);
        }
    }

    //metoda dodająca labele z numerami dni miesiąca
    private void AddDayLabelToWrap(int startDayAtPanel, int totalDaysInMonth)
    {
        foreach (WrapPanel wrap in daysList)
        {
            wrap.Children.Clear();
        }
        for (int i = 1; i <= totalDaysInMonth; i++)
        {
            var lab = new Label();
            lab.Name = $"lblDay{i}";
            lab.Content = i;
            lab.HorizontalContentAlignment = HorizontalAlignment.Right;
            daysList[(i - 1) + (startDayAtPanel - 1)].Children.Add(lab);
        }
    }

    
    private void ButtonPrevMonth_Click(object sender, RoutedEventArgs e)
    {
        PreviousMonth();
    }

    private void ButtonNextMonth_Click(object sender, RoutedEventArgs e)
    {
        NextMonth();
    }

    private void ButtonToday_Click(object sender, RoutedEventArgs e)
    {
        Today();
    }

}
}

Solution

  • I made the sample you need and put it on the GitHub.

    Here.
    https://github.com/ncoresoftsource/stackoverflowsample/tree/main/src/answers/custom-calendar-app

    This is not as simple as I thought. And like other people's advice, writing the UI in the behind code is not a good way in the long run, so I hope you study this sample source code that I made for you!

    Just point!

    • Using ListBox

      public class CalendarBox : ListBox
      {
      
      }
      
    • Style & Template

      <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                          xmlns:ctrl="clr-namespace:CalendarCore.Controls">
      
          <Style TargetType="{x:Type Label}" x:Key="LABEL.WEEK">
              <Setter Property="VerticalContentAlignment" Value="Center"/>
              <Setter Property="HorizontalContentAlignment" Value="Center"/>
              <Setter Property="BorderThickness" Value="0 0 1 1"/>
              <Setter Property="BorderBrush" Value="#DDDDDD"/>
              <Setter Property="Background" Value="#F1F1F1"/>
              <Setter Property="Padding" Value="0 4 0 4"/>
              <Setter Property="Template">
                  <Setter.Value>
                      <ControlTemplate TargetType="{x:Type Label}">
                          <Border Background="{TemplateBinding Background}"
                                  BorderThickness="{TemplateBinding BorderThickness}"
                                  BorderBrush="{TemplateBinding BorderBrush}"
                                  Padding="{TemplateBinding Padding}">
                              <TextBlock Text="{TemplateBinding Content}" 
                                         VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                         HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
                          </Border>
                      </ControlTemplate>
                  </Setter.Value>
              </Setter>
          </Style>
      
          <Style TargetType="{x:Type ListBoxItem}" x:Key="LBXI.DAY">
              <Setter Property="Background" Value="Transparent"/>
              <Setter Property="BorderThickness" Value="0 0 1 1"/>
              <Setter Property="BorderBrush" Value="#DDDDDD"/>
              <Setter Property="Padding" Value="10"/>
              <Setter Property="Template">
                  <Setter.Value>
                      <ControlTemplate TargetType="{x:Type ListBoxItem}">
                          <Border Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}"
                                  BorderBrush="{TemplateBinding BorderBrush}"
                                  Padding="{TemplateBinding Padding}">
                              <TextBlock Text="{Binding Day}" />
                          </Border>
                          <ControlTemplate.Triggers>
                              <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                                  <Setter Property="Background" Value="#FAFAFA"/>
                              </Trigger>
                              <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                                  <Setter Property="Background" Value="#F1F1F1"/>
                              </Trigger>
                              <DataTrigger Binding="{Binding IsLastMonth}" Value="True">
                                  <Setter Property="Foreground" Value="#BBBBBB"/>
                              </DataTrigger>
                              <DataTrigger Binding="{Binding IsNextMonth}" Value="True">
                                  <Setter Property="Foreground" Value="#BBBBBB"/>
                              </DataTrigger>
                          </ControlTemplate.Triggers>
                      </ControlTemplate>
                  </Setter.Value>
              </Setter>
          </Style>
      
          <Style TargetType="{x:Type ctrl:CalendarBox}">
              <Setter Property="AlternationCount" Value="2"/>
              <Setter Property="ItemContainerStyle" Value="{StaticResource LBXI.DAY}"/>
              <Setter Property="Margin" Value="10"/>
              <Setter Property="BorderThickness" Value="1 1 0 0"/>
              <Setter Property="BorderBrush" Value="#DDDDDD"/>
              <Setter Property="Template">
                  <Setter.Value>
                      <ControlTemplate TargetType="{x:Type ctrl:CalendarBox}">
                          <Border BorderThickness="{TemplateBinding BorderThickness}"
                                  BorderBrush="{TemplateBinding BorderBrush}">
                              <Grid>
                                  <Grid.RowDefinitions>
                                      <RowDefinition Height="Auto"/>
                                      <RowDefinition Height="*"/>
                                  </Grid.RowDefinitions>
                                  <UniformGrid Columns="7">
                                      <Label Style="{StaticResource LABEL.WEEK}" Content="MON" Background="#FFFFEAEA"/>
                                      <Label Style="{StaticResource LABEL.WEEK}" Content="TUE" Background="#FFE8F9FF"/>
                                      <Label Style="{StaticResource LABEL.WEEK}" Content="WED" Background="#FFE1F1C5"/>
                                      <Label Style="{StaticResource LABEL.WEEK}" Content="THU" Background="#FFFFD7D7"/>
                                      <Label Style="{StaticResource LABEL.WEEK}" Content="FRI" Background="#FFE9F9E4"/>
                                      <Label Style="{StaticResource LABEL.WEEK}" Content="SAT" Background="#FFF7F6E3"/>
                                      <Label Style="{StaticResource LABEL.WEEK}" Content="SUN" Background="#FFC4DAE4"/>
                                  </UniformGrid>
      
                                  <ItemsPresenter Grid.Row="1"/>
                              </Grid>
                          </Border>
                      </ControlTemplate>
                  </Setter.Value>
              </Setter>
              <Setter Property="ItemsPanel">
                  <Setter.Value>
                      <ItemsPanelTemplate>
                          <UniformGrid Columns="7"/>
                      </ItemsPanelTemplate>
                  </Setter.Value>
              </Setter>
          </Style>
      </ResourceDictionary>
      
    • MainWindow (.xaml)

      <Grid>
          <Grid.RowDefinitions>
              <RowDefinition Height="Auto"/>
              <RowDefinition/>
          </Grid.RowDefinitions>
          <StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right">
              <Button x:Name="btnPreview" Content="Preview" Margin="4" Padding="4"/>
              <Button x:Name="btnNext" Content="Next" Margin="4" Padding="4"/>
          </StackPanel>
          <ctrl:CalendarBox Grid.Row="1" x:Name="calendar"/>
      </Grid>
      
    • Code Behind (.cs)

      using System;
      using System.Collections;
      using System.Collections.Generic;
      using System.Windows;
      using CalendarCore.Enums;
      using CalendarCore.Models;
      
      namespace CalendarDemo.Basic
      {
          public partial class MainWindow : Window
          {
              private int Year;
              private int Month;
      
              public MainWindow()
              {
                  InitializeComponent();
      
                  Year = DateTime.Now.Year;
                  Month = DateTime.Now.Month;
      
                  Loaded += (s,e)=> Refresh(CalendarMove.None);
                  btnPreview.Click += (ps, pe) => Refresh(CalendarMove.Preview);
                  btnNext.Click += (ns, ne) => Refresh(CalendarMove.Next);
              }
      
              private void Refresh(CalendarMove move)
              {
                  DateTime currentDateTime = new DateTime(Year, Month, 1);
                  int moveMonth = 0;
                  switch (move)
                  {
                      case CalendarMove.None: moveMonth = 0; break;
                      case CalendarMove.Preview: moveMonth = -1; break;
                      case CalendarMove.Next: moveMonth = 1; break;
                  }
      
                  Year = currentDateTime.AddMonths(moveMonth).Year; 
                  Month = currentDateTime.AddMonths(moveMonth).Month;
      
                  calendar.ItemsSource = GenerateCalendar(Year, Month);
              }
      
              private IEnumerable GenerateCalendar(int year, int month)
              {
                  List<DayModel> days = new List<DayModel>();
      
                  // Step 1. Add days of last month.
                  AddDaysOfLastMonth(year, month, ref days);
      
                  // Step 2. Add days of current mon.th
                  AddDaysOfCurrentMonth(year, month, ref days);
      
                  // Step 3. Add days of next month.
                  AddDaysOfNextMonth(year, month, ref days);
                  return days;
              }
      
              private void AddDaysOfLastMonth(int year, int month, ref List<DayModel> days)
              {
                  var lastMonth = new DateTime(year, month, 1).AddMonths(-1);
      
                  int dayStarting;
                  int lastDayOfLastMonth = DateTime.DaysInMonth(lastMonth.Year, lastMonth.Month);
                  DayOfWeek firstDayOfWeek = new DateTime(year, month, 1).DayOfWeek;
      
                  switch (firstDayOfWeek)
                  {
                      case DayOfWeek.Monday: dayStarting = 0; break;
                      case DayOfWeek.Tuesday: dayStarting = 1; break;
                      case DayOfWeek.Wednesday: dayStarting = 2; break;
                      case DayOfWeek.Thursday: dayStarting = 3; break;
                      case DayOfWeek.Friday: dayStarting = 4; break;
                      case DayOfWeek.Saturday: dayStarting = 5; break;
                      case DayOfWeek.Sunday: dayStarting = 6; break;
                      default: dayStarting = 0;break;
                  }
      
                  for (int i = 1; i <= dayStarting; i++)
                  {
                      days.Add(new DayModel
                      {
                          Date = new DateTime(lastMonth.Year, lastMonth.Month, lastDayOfLastMonth + i - dayStarting),
                          IsLastMonth = true
                      });
                  }
              }
      
              private void AddDaysOfCurrentMonth(int year, int month, ref List<DayModel> days)
              {
                  int lastDay = DateTime.DaysInMonth(year, month);
                  for (int i = 1; i <= lastDay; i++)
                  {
                      days.Add(new DayModel { Date = new DateTime(year, month, i) });
                  }
              }
      
              private void AddDaysOfNextMonth(int year, int month, ref List<DayModel> days)
              {
                  var nextMonth = new DateTime(year, month, 1).AddMonths(1);
                  var lastDayofCurrentMonth = DateTime.DaysInMonth(year, month);
      
                  int dayStarting;
                  DayOfWeek lastDayOfWeek = new DateTime(year, month, lastDayofCurrentMonth).DayOfWeek;
      
                  switch (lastDayOfWeek)
                  {
                      case DayOfWeek.Monday: dayStarting = 6; break;
                      case DayOfWeek.Tuesday: dayStarting = 5; break;
                      case DayOfWeek.Wednesday: dayStarting = 4; break;
                      case DayOfWeek.Thursday: dayStarting = 3; break;
                      case DayOfWeek.Friday: dayStarting = 2; break;
                      case DayOfWeek.Saturday: dayStarting = 1; break;
                      case DayOfWeek.Sunday: dayStarting = 0; break;
                      default: dayStarting = 0; break;
                  }
      
                  for (int i = 1; i <= dayStarting; i++)
                  {
                      days.Add(new DayModel
                      {
                          Date = new DateTime(nextMonth.Year, nextMonth.Month, i),
                          IsNextMonth = true
                      });
                  }
              }
          }
      }
      
    • Model

      public class DayModel
      {
          public DateTime Date { get; set; }
      
          public int Year => Date.Year;
          public int Month => Date.Month;
          public int Day => Date.Day;
      
          public bool IsLastMonth { get; set; }
          public bool IsCurrentMonth { get; set; }
          public bool IsNextMonth { get; set; }
      }