Печать календаря на 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. Это одна из немногих вещей, о которых каждая страна согласна.)

Следующий код показывает метод 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, если строка календаря заполнена.

Источник: http://csharphelper.com/blog/2016/01/print-a-calendar-in-c/

1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (Пока оценок нет)
Adblock
detector