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.
After clicking next Month Button, February should start in Thursday.
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();
}
}
}
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; }
}