Исходные коды программ и игр

Программирование - работа и хобби

Часы со стрелками на платформе WPF

Язык программирования C#

Приложение на платформе WPF

Приложение часы со стрелками на платформе WPFПриложение «Часы со Стрелками» на платформе WPF (Windows Presentation Foundation) .NET Core в качестве знакомства с новой моделью создания графических интерфейсов. Окно часов круглой формы, реалистичны настолько, что просто хочется снять их с экрана компьютера и повесить на стену. Недалеко то будущее, когда так и будет. Красочный интерфейс часов создан с применением только стандартных элементов, включённых в каркас WPF.

В отличие от Windows Forms графика в WPF полностью основана на технологии DirectX. Составляющие интерфейса пользователя, кнопки, диалоговые окна, списки, текстовые блоки и даже сам текст прорисовывается аппаратно-ускоренным способом. Причём нет никаких методов OnPaint(), где мы должны создавать код рисования. На мониторах с разным количеством точек на дюйм DPI, интерфейс приложений WPF сохраняет неизменное качество графики.

Для сравнения графических возможностей Windows Presetation Foundation и C++ MFC программы посмотрите исходник часов на GDI

В приложении имитации стрелочных часов демонстрируется модель компоновки WPF, совершенно отличной от принципа размещения элементов управления Windows Forms. Создаётся первый и единственный контейнер, все остальные контейнеры и элементы управления вкладываются в главный. Каждый контейнер WPF обладает собственной логикой размещения дочерних элементов в пределах своего пространства. В WPF рекомендуется использовать относительные координаты. Для изменения позиции элементов используются величины: Margin, Padding, HorizontalAlignment, VerticalAlignment контейнеров и их вложений.

Окно круглой формы

В WPF легко создавать окна фигурной формы. Разработчики Windows Presentation Foundation добавили свойство Window.AllowsTransparency специально для упрощения создания окон непрямоугольной формы. Если свойству присвоить значение истинно, то окно становится прозрачным, но всё что расположено на окне видимо. Для формирования круглого окна я использовал элемент Border с закруглёнными углами. При изменении свойства Border.CornerRadius можно получать циферблат прямоугольной, с закругленными углами и даже круглой формы.

Чтобы получить окно произвольной формы, необходимо присвоить некоторым свойствам окна определенные значения: удалить или сделать фон окна прозрачным, скрыть заголовок и границу окна, позволить окну быть прозрачным.

<Window 
...
Background="{x:Null}" 
WindowStyle="None" 
AllowsTransparency="True">
...
</Window>

Код графики в XAML

Интерфейс программ WPF можно создавать двумя способами: используя расширяемый язык разметки в текстовых файлах XAML и классическим программным способом. В прикрепленных исходниках используются оба способа построения интерфейса. Язык XAML базируется на структуре файлов XML со своими специфическими расширениями. Файлы XAML отделяют графическую часть приложения от программной. Такое разделение даёт преимущество при командной разработке, когда дизайнеры могут сосредоточиться над графикой, а программисты работают с кодом. Индивидуальные разработчики также получают эффект повышения качества и скорости разработки, работая отдельно с графическим и программным модулями.

Для работы с графикой приложения используя язык XAML в комплект MS Visual Studio входит инструмент для дизайнеров Blend. Возможности его гораздо шире, чем редактор встроенный в Visual Studio. Например, Blend позволяет визуализировать создание анимации, осуществлять её предпросмотр и редактирование существенно увеличивая скорость разработки.

XAML код графики приложения

<Window x:Class="WpfClock.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfClock"
        mc:Ignorable="d"
        Title="MainWindow" Height="650" Width="650" WindowStyle="None"  AllowsTransparency="True" 
            Background="{x:Null}" >
    <Grid>
        <Border x:Name="Forma" BorderThickness="50,50,50,50" CornerRadius="325" Padding="5,5,5,2">
            <Border.Background>
                <ImageBrush ImageSource="/tiger.jpg"/>
            </Border.Background>
            <Border.BorderBrush>
                <RadialGradientBrush>
                    <GradientStop Color="#FF8A9DF5" Offset="0.854"/>
                    <GradientStop Color="#FF052A7A" Offset="1"/>
                    <GradientStop Color="#FF09338D" Offset="0.772"/>
                </RadialGradientBrush>
            </Border.BorderBrush>

            <!-- Циферблат часов -->
            <Grid x:Name="ClockFace">

                <!-- Цифра 1 -->
                <Border HorizontalAlignment="Center" RenderTransformOrigin="0.5,0.5">
                    <Border.RenderTransform>
                        <TransformGroup>
                            <RotateTransform Angle="30"/>
                        </TransformGroup>
                    </Border.RenderTransform>
                    <TextBlock Text="1" FontSize="60" FontWeight="Bold" 
                           RenderTransformOrigin="0.5,0.5" FontFamily="Book Antiqua"
                           VerticalAlignment="Top" HorizontalAlignment="Center">
                            <TextBlock.RenderTransform>
                                <TransformGroup>
                                    <RotateTransform Angle="-30"/>
                                </TransformGroup>
                            </TextBlock.RenderTransform>

                        </TextBlock>
                    </Border>
                <!-- /Цифра 1 -->

               ...
			   
                <!-- Часовая стрелка -->
                <Border x:Name="HourArrow" HorizontalAlignment="Center" 
                      RenderTransformOrigin="0.5,0.5" Width="10" Padding="0,85,0,0">
                    <Border BorderBrush="#FF706F6F" HorizontalAlignment="Stretch" 
                          VerticalAlignment="Top" Background="#FF302E2E" Height="220"/>
                </Border>
                <!-- /Часовая стрелка -->

               ...

                <!-- Болтик -->
                <Border Width="25" Height="25" HorizontalAlignment="Center" 
                      VerticalAlignment="Center" CornerRadius="15,15,15,15" 
                        BorderThickness="4,4,4,4" BorderBrush="#FF1F1F1F" Background="#FFF9F6F6"/>
                <!-- /Болтик -->
            </Grid>
            <!-- /Циферблат часов -->

        </Border>
    </Grid>
</Window>

Программная часть интерфейса

Несмотря на то, что интерфейс приложения Windows Presentation Foundation рекомендуется реализовывать в файле XAML, иногда рациональней всё же создавать графику программным способом. Речь идёт о многократно повторяющихся элементах интерфейса. Например, в прикрепленном приложении это метки циферблата: 12 часовых и 60 минутных. В программном коде, в цикле мы их легко реализуем.

// Минутная маркировка циферблата
void MinuteMarks()
{
    for (int i = 0; i < 60; i++)
    {
        // Контейнер для метки. 
        // Необходим для вращения пространства
        // в котором расположена декоративная метка. 
        var b = new Border()
        {
            HorizontalAlignment = HorizontalAlignment.Stretch,
            VerticalAlignment = VerticalAlignment.Center,
            RenderTransformOrigin = new Point(0.5, 0.5),

            // Базовая ориентация контейнера метки горизонтальное,
            // поэтому высота играет роль толщины метки.
            Height = 2
        };


        // В роли метки выступает элемент Border.
        var b1 = new Border()
        {
            Background = Brushes.Brown,
            
            // Базовая ориентация горизонтальная,
            // поэтому ширина визуально является длиной метки.
            Width = 10,
            HorizontalAlignment = HorizontalAlignment.Right
        };


        b.Child = b1;


        // Вращаем только сам контейнер.
        var rotate = new RotateTransform(i * 6);
        b.RenderTransform = rotate;


        // Исключаем из маркировки метки 0 и 
        // через каждые 30 градусов. 
        // В этих местах будут часовые метки.
        if (i * 6 % 30 != 0)
        {
            // Добавляем на циферблат контейнеры с метками.
            ClockFace.Children.Add(b);
        }
    }

}

Принцип вращения стрелок

Схема вращения стрелок часов Анимационная графика стрелок приложения-часов построена так: создаётся контейнер-носитель, в него добавляются декоративные элементы. Так формируется статическая графика стрелок. На рисунке показана секундная стрелка. Корпус стрелки состоит из двух элементов Border, которые создают визуализацию указателя и противовеса. Благодаря графической технологии WPF "запчасти" часов выглядят очень реалистично.

Слаженное вращение всех частей стрелок обеспечивает контейнер-носитель. Функцию контейнера выполняет также элемент управления Border. Относительно контейнера дочерние элементы неподвижны. Контейнер вращается относительно центра циферблата, сохраняя положение своих элементов. Такой способ анимации стрелок значительно упрощает создание целостного вращательного движения.

Код XAML секундной стрелки:
<!-- Секундная стрелка -->

<!-- Контейнер -->
<Border x:Name="SecondArrow" HorizontalAlignment="Center" 
        Width="2" Padding="0,15,0,0" RenderTransformOrigin="0.5,0.5">
    <Grid>
        <!-- Указатель -->
        <Border  BorderBrush="Black"  HorizontalAlignment="Stretch" 
            VerticalAlignment="Top" Background="#FFB84F31" Height="300"/>
        <!-- /Указатель -->
        <!-- Противовес -->
        <Border Background="#FFB84F31" VerticalAlignment="Center" Width="10" 
            Height="50" HorizontalAlignment="Center" Margin="-5,120,-5,0" 
            CornerRadius="5,5,5,5"></Border>
        <!-- /Противовес -->
    </Grid>
</Border>
<!-- /Контейнер -->

<!-- /Секундная стрелка -->

Для вращения контейнера точно вокруг своего центра определяем свойство RenderTransformOrigin="0.5,0.5". Интересная деталь кода противовеса: свойство Margin имеет отрицательные значения. Такое делается тогда, когда необходимо "раздуть" элемент за пределы пространства родителя.

Механизм вращения стрелок

Для вращения стрелок часам требуется часовой механизм. Электродвигателем в приложении служат события таймера. Во время тика таймера происходит вычисление углов поворота всех стрелок часов. Часы приложения синхронизируются с системным временем компьютера. Таймер работает с разрешением один тик в 100 миллисекунд, и секундная стрелка движется синхронно с секундами цифровых часов операционной системы. Положения стрелок исчисляются из базового угла поворота 6 градусов. Секундная стрелка за 60 шагов проходит полный круг (360°) циферблата часов.

Листинг кода вычисления углов
private void Timer_Tick(object sender, EventArgs e)
{
    var rotateSecondArrow = new RotateTransform();
    var rotateMinuteArrow = new RotateTransform();
    var rotateHourArrow = new RotateTransform();

    // Данные текущего времени.
    int sec = DateTime.Now.Second;
    int min = DateTime.Now.Minute;
    int hour = DateTime.Now.Hour;


    // Вычисленный угол для секундной стрелки.
    rotateSecondArrow.Angle = baseAngleNumberSystem * sec;

    // Вращение стрелки на вычисленный угол.
    SecondArrow.RenderTransform = rotateSecondArrow;


    // Угол минутной стрелки от количества полных минут плюс
    // угол секунд приведенный к долям текущей минуты.
    rotateMinuteArrow.Angle = (min * baseAngleNumberSystem) + (rotateSecondArrow.Angle / 60.0);

    MinuteArrow.RenderTransform = rotateMinuteArrow;


    // Данные часа конвертируем в 12-часовой вид,
    // вычисляем угол полных часов плюс
    // угол минут приведенный к долям текущего часа.
    rotateHourArrow.Angle = (hour - 12) * baseAngleHour + rotateMinuteArrow.Angle / 12;

    HourArrow.RenderTransform = rotateHourArrow;

}

Исходник программы часов на WPF

Архив файла содержит исходник программы часов. Инструмент программирования MS Visual Studio 2019. Среда .NET Core 3.1

Файл: WpfClock-vs2019.zip
Размер: 283 Кбайт
Загрузки: 46