Печать календаря на 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, если строка календаря заполнена.