Печать календаря на C#
Этот пример печатает календарь, содержащий некоторый текст для каждого дня. Это в основном вопрос отслеживания того, как рисовать вещи, но это также требует от вас обработки некоторых запутанных вопросов интернационализации, и есть одна потенциальная возможность.
В программе используются несколько методов, включая метод, описанный в сообщении Получить имя первого дня недели на C# .
Одна из больших проблем заключается в том, что разные страны начинают неделю с разных дней. Например, в Соединенных Штатах неделя начинается с воскресенья, но в Германии и Австралии начинается с понедельника.
Программа использует следующие две переменные для хранения информации о календаре.
// Данные календаря. DateTime FirstOfMonth; private string[] CalendarData;
В переменной FirstOfMonth хранится первая дата месяца, который должен быть напечатан. Массив CalendarData содержит текст для отображения в каждой из дат месяца. В этом примере вы можете выбрать месяц и год. Программа инициализирует этот массив случайным образом, хотя в реальном приложении вы хотели бы использовать напоминания о назначении и другой полезный текст.
При нажатии кнопки «Предварительный просмотр» программа считывает выбранную дату и создает случайные данные. Затем он вызывает метод ShowDialog для диалогового окна предварительного просмотра, который я создал во время разработки. Также во время разработки я создал PrintDocument и установил для него свойство Document диалогового окна.
Когда программа вызывает ShowDialog, в диалоговом окне используется PrintDocument, чтобы сгенерировать распечатку, подняв некоторые события. Первым событием, используемым в этом примере, является QueryPageSettings, который обрабатывается следующим обработчиком событий.
// Печать в ландшафтном режиме.
private void pdocCalendar_QueryPageSettings(object sender,
QueryPageSettingsEventArgs e)
{
e.PageSettings.Landscape = true;
}
Этот код просто выводит распечатку в альбомном режиме. Если вы хотите печатать в портретном режиме, просто закомментируйте эту строку.
После завершения этого события PrintDocument вызывает событие PrintPage, чтобы сделать рисунок. Этот обработчик событий PrintPage этого примера просто вызывает следующий метод DrawCalendar.
// Нарисуйте календарь так же сильно, как posisble.
private void DrawCalendar(Graphics gr, RectangleF bounds,
DateTime first_of_month, string[] date_data)
{
// Сделать строки и столбцы максимально большими.
float col_wid = bounds.Width / 7f;
// Посмотрим, сколько недель нам понадобится.
int num_rows = NumberOfWeekRows(first_of_month);
// Добавьте дополнительную строку для месяца и года вверху.
num_rows++;
// Вычислим высоту строки.
float row_hgt = bounds.Height / (float)num_rows;
// Нарисуем месяц и год.
float x = bounds.X;
float y = bounds.Y;
RectangleF rectf = new RectangleF(
x, y, bounds.Width, row_hgt / 2f);
DrawMonthAndYear(gr, rectf, first_of_month);
y += row_hgt / 2f;
// Нарисуем имена дней.
DrawWeekdayNames(gr, x, y, col_wid, row_hgt / 2f);
y += row_hgt / 2f;
// Нарисуем ячейки даты.
DrawDateData(first_of_month, date_data,
gr, x, y, col_wid, row_hgt);
// Настройте календарь.
gr.DrawRectangle(Pens.Black,
bounds.X, bounds.Y, bounds.Width, bounds.Height);
}
Этот метод делает календарь максимально возможным, вставляя его в прямоугольник, который он получает в качестве параметра. Для этого он делит ширину области на семь четных столбцов.
Затем метод вызывает метод NumberOfWeekRows, описанный в скором времени, чтобы выяснить, сколько строк займет месяц для всех его дней. Он добавляет один к числу недель, чтобы предоставить место для названия календаря, а затем делит доступную высоту на результирующее число.
Затем код вызывает методы DrawMonthAndYear и DrawWeekdayNames, чтобы нарисовать названия заголовков и дней недели в их соответствующих положениях.
Затем метод вызывает DrawDateData, чтобы нарисовать ячейки даты. Он заканчивается рисованием окна вокруг всего календаря.
Следующий код показывает метод NumberOfWeekRows.
// Возвращает количество недельных строк, необходимых в этом месяце.
private int NumberOfWeekRows(DateTime first_of_month)
{
// Получаем количество дней в месяце.
int num_days = DateTime.DaysInMonth(
first_of_month.Year, first_of_month.Month);
// Добавить номер столбца (пронумерованный от 1)
// в первый день месяца.
num_days += DateColumn(first_of_month);
// Разделим на 7 и округлим.
return (int)Math.Ceiling(num_days / 7f);
}
Этот метод использует DateTime.DaysInMonth, чтобы узнать, сколько дней в этом месяце. Затем он использует метод DateColumn, описанный ниже, чтобы получить номер столбца в первый день месяца и добавляет его к числу дней в месяце. В основном это добавляет день к месяцу для каждого дня, который предшествует первому дню месяца.
Например, предположим, что неделя начинается с воскресенья, а первый день месяца - вторник. Затем код добавляет 2 дня (номера столбцов - воскресенье = 0, понедельник = 1, вторник = 2), чтобы освободить место для воскресенья и понедельника, которые выходят до первого дня.
После добавления дополнительных поддельных дней метод просто делит на 7, округляет и возвращает результат.
В следующем коде показан метод DateColumn.
// Возвращает номер столбца для этой даты в текущей локали.
private int DateColumn(DateTime date)
{
int col =
(int)date.DayOfWeek -
(int)CultureInfo.CurrentCulture.
DateTimeFormat.FirstDayOfWeek;
if (col < 0) col += 7;
return col;
}
Метод DateColumn возвращает номер столбца для определенной даты. Сначала он получает дату DayOfWeek. Этот номер является индексом даты в массиве имен дня в культуре и этот массив всегда начинается с воскресенья в позиции массива 0. Например, если днем является вторник, то это число равно 2.
Но неделя не обязательно начинается с воскресенья, в зависимости от локали, поэтому код вычитает индекс первого дня недели.
Например, предположим, что вы находитесь в Австралии, поэтому первый день недели - понедельник с индексом 1. Тогда, если дата - вторник, номер столбца 2 - 1 = 1. Это имеет смысл, потому что в Австралии первая колонка проходит в понедельник, а вторая - во вторник.
Теперь, если дата была в воскресенье, а неделя начинается в понедельник, номер столбца равен -1. В этом случае код добавляет 7, чтобы получить столбец номер 6, то есть эта дата принадлежит в последнем столбце недели.
Теперь, если дата была в воскресенье, а неделя начинается в понедельник, номер столбца равен -1. В этом случае код добавляет 7, чтобы получить столбец номер 6, то есть эта дата принадлежит в последнем столбце недели.
...
// Нарисуем месяц и год.
private void DrawMonthAndYear(Graphics gr,
RectangleF rectf, DateTime date)
{
using (StringFormat sf = new StringFormat())
{
// Центрируем текст.
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
string[] month_names =
CultureInfo.CurrentCulture.DateTimeFormat.MonthNames;
string title = month_names[date.Month - 1] +
" " + date.Year.ToString();
// Найти самый большой шрифт, который подойдет.
int font_size = FindFontSize(gr, rectf,
"Times New Roman", title);
// Рисуем текст.
gr.FillRectangle(Brushes.LightBlue, rectf);
using (Font font = new Font("Times New Roman", font_size))
{
gr.DrawString(title, font, Brushes.Blue, rectf, sf);
}
}
}
Этот метод сначала превращает объект StringFormat в центр текста.
Затем он использует массив MonthNames текущей культуры, чтобы получить название выбранного месяца. Вот потенциальная добыча. Свойство DateTime структуры DateTime дает номер месяца от 1 до 12. Как и почти каждый другой массив в C#, массив имен месяцев начинается с индекса 0, поэтому код вычитает 1 от номера месяца, чтобы получить правильное имя месяца. Затем он использует имя месяца и год даты для создания строки заголовка календаря.
Затем код вызывает метод FindFontSize, чтобы найти самый большой шрифт, который он может использовать, в то время как строка заголовка вписывается в допустимую область. Метод FindFontSize, который здесь не показан, просто пытается увеличить и увеличить шрифты, пока не найдет тот, который не подходит, и затем вернет последний размер, который соответствовал.
После того, как он нашел размер шрифта, метод рисует заголовок календаря.
Следующий код показывает, как метод DrawWeekdayNames рисует имена дней недели по столбцам календаря.
// Нарисуйте имена дней недели.
private void DrawWeekdayNames(Graphics gr,
float x, float y, float col_wid, float hgt)
{
// Найти имя самого большого дня.
float max_wid = 0;
string[] day_names =
CultureInfo.CurrentCulture.DateTimeFormat.DayNames;
string widest_name = day_names[0];
using (Font font = new Font("Times New Roman", 10))
{
foreach (string name in day_names)
{
SizeF size = gr.MeasureString(name,font);
if (max_wid < size.Width)
{
max_wid = size.Width;
widest_name = name;
}
}
}
// Найдите самый большой размер шрифта, который будет соответствовать.
RectangleF rectf = new RectangleF(x, y, col_wid, hgt);
int font_size = FindFontSize(gr, rectf,
"Times New Roman", widest_name);
// Нарисуем имена дней.
using (Font font = new Font("Times New Roman", font_size))
{
using (StringFormat sf = new StringFormat())
{
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
int index = (int)CultureInfo.CurrentCulture.
DateTimeFormat.FirstDayOfWeek;
for (int i = 0; i < 7; i++)
{
gr.FillRectangle(Brushes.LightBlue, rectf);
gr.DrawString(day_names[index], font,
Brushes.Blue, rectf, sf);
index = (index + 1) % 7;
rectf.X += col_wid;
}
}
}
}
Сначала код получает массив DayNames текущей культуры, который содержит имена будних дней. Он создает шрифт и затем перебирает имена, измеряя каждый, чтобы увидеть, что является самым широким, когда нарисовано в шрифте.
Затем код вызывает FindFontSize, чтобы увидеть, насколько большой он может нарисовать наибольшее имя дня недели, чтобы он помещался в пространство над столбцом.
Далее код перебирает имена и рисует их по столбцу. Массив целых дней в буддизме всегда содержит имена в порядке: воскресенье, понедельник, вторник, ..., суббота. Значение DateTimeFormat.FirstDayOfWeek в культуре дает вам индекс в этом массиве первого дня недели. Например, это значение равно 0 в Соединенных Штатах и 1 в Австралии.
Программа начинается с переменной index, равной культуре FirstDayOfWeek, а затем отображает 7 значений, при необходимости обертывая индекс 0.
(Обратите внимание, что в программе предполагается, что есть семь дней в неделю. Это похоже на каждую культуру, поддерживаемую .NET. Это одна из немногих вещей, о которых каждая страна согласна.) p >
Следующий код показывает метод DrawDateData.
private void DrawDateData(DateTime first_of_month,
string[] date_data, Graphics gr, float x, float y,
float col_wid, float row_hgt)
{
RectangleF date_rectf =
new RectangleF(x, y, col_wid / 3f, row_hgt / 4f);
RectangleF data_rectf =
new RectangleF(x, y, col_wid, row_hgt * 0.75f);
int font_size = FindFontSize(gr, date_rectf,
"Times New Roman", "30");
int col = DateColumn(first_of_month);
using (Font number_font =
new Font("Times New Roman", font_size))
{
using (Font data_font =
new Font("Times New Roman", font_size * 0.75f))
{
using (StringFormat ul_sf = new StringFormat())
{
ul_sf.Alignment = StringAlignment.Near;
ul_sf.LineAlignment = StringAlignment.Near;
ul_sf.Trimming = StringTrimming.EllipsisWord;
ul_sf.FormatFlags = StringFormatFlags.LineLimit;
int num_days = DateTime.DaysInMonth(
first_of_month.Year, first_of_month.Month);
for (int day_num = 0; day_num < num_days; day_num++)
{
RectangleF cell_rectf = new RectangleF(
x + col * col_wid, y, col_wid, row_hgt);
gr.DrawRectangle(Pens.Black,
cell_rectf.X, cell_rectf.Y,
cell_rectf.Width, cell_rectf.Height);
date_rectf.X = cell_rectf.X;
date_rectf.Y = cell_rectf.Y;
gr.DrawString((day_num + 1).ToString(),
number_font, Brushes.Blue, date_rectf, ul_sf);
data_rectf.X = x + col * col_wid;
data_rectf.Y = y + row_hgt * 0.25f;
gr.DrawString(date_data[day_num],
data_font, Brushes.Black, data_rectf, ul_sf);
col = (col + 1) % 7;
if (col == 0) y += row_hgt;
}
}
}
}
}
Код начинается с определения прямоугольника date_rectf, который занимает верхнюю четверть и левую треть области, разрешенной для ячейки с верхним левым номером. Это область, где будет отображаться номер даты.
Затем код создает прямоугольник data_rectf для хранения данных даты ниже прямоугольника date_rectf.
Далее код использует метод FindFontSize, чтобы найти самый большой шрифт, который он может использовать, чтобы соответствовать тексту «30» в прямоугольнике числа.
Затем код вызывает DateColumn (описанный ранее), чтобы увидеть, какой столбец должен содержать первый день месяца.
Далее код создает шрифты, чтобы нарисовать даты и текст даты. Он создает объект StringFormat, который помещает текст в верхний левый угол прямоугольника форматирования. Он устанавливает свойства объекта Обрезка и FormatFlags, чтобы текст заканчивался эллипсисом после последнего слова, которое может поместиться, и новая строка не запускается, если она не будет полностью соответствовать в прямоугольнике форматирования.
Затем код получает число дней в этом месяце и готов начать рисовать даты.
Переменная day_num пересекает от 0 до единицы меньше, чем число дней в месяце. Для каждого дня код устанавливает положение прямоугольника cell_rectf. Его координата X задается числом столбцов col, умноженным на ширину столбца. Его позиция Y сохраняется в переменной y. После того, как он задает позицию прямоугольника для ячейки, код очерчивает ее.
Далее код позиционирует прямоугольник числа даты и рисует в нем дату. Затем он делает то же самое для данных даты, позиционирует соответствующий прямоугольник, а затем рисует данные.
После того, как он закончил рисовать эту дату, код добавляет 1 к номеру столбца, обертывая обратно до 0, если строка календаря заполнена.
